feat(ui): share indicator detail panels

Port the authored Link Status, Vitae, and Mini Game detail roots and register every indicator page with retail's one-active gmPanelUI owner. Helpful/Harmful and the new pages now replace Inventory, Character, or Magic at one canonical window position while preserving the DAT restore-previous flag.

Correct the retail ping wire to its payload-free request/response, publish measured RTT, and port Vitae recovery XP from the live modifier and player properties. Keep transport packet-loss averaging and mini-game gameplay explicitly tracked under AP-110.

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

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 10:27:41 +02:00
parent 52c529be86
commit a96767ba6d
28 changed files with 6539 additions and 32 deletions

View file

@ -25,6 +25,17 @@ Named retail functions:
- `gmUIElement_BurdenIndicator::Update @ 0x004E6CE0`
- `LinkStatusHolder::GetConnectionStatus @ 0x00411380`
- `LinkStatusHolder::OnHeartbeat @ 0x004113D0`
- `gmPanelUI::SetupChildren @ 0x004BC9E0`
- `gmPanelUI::RecvNotice_SetPanelVisibility @ 0x004BC6F0`
- `gmLinkStatusUI::PostInit @ 0x004AADB0`
- `gmLinkStatusUI::Update @ 0x004AAEA0`
- `gmLinkStatusUI::ListenToElementMessage @ 0x004AB0B0`
- `gmLinkStatusUI::ListenToGlobalMessage @ 0x004AB110`
- `gmLinkStatusUI::RecvNotice_Ping @ 0x004AB150`
- `gmVitaeUI::PostInit @ 0x004A7240`
- `gmVitaeUI::Update @ 0x004A7400`
- `VitaeSystem::VitaeCPPoolThreshold @ 0x005C8FD0`
- `CM_Character::Event_RequestPing @ 0x006A19A0`
- `gmEffectsUI::SpellEffectMatchesUIType @ 0x004B76C0`
- `gmGamePlayUI::RecvNotice_EndCharacterSession @ 0x004EBEA0`
@ -40,6 +51,11 @@ The installed retail LayoutDesc establishes the fixed child order and art:
| 105 | `0x100000F3` | `0x10000004` | mini-game |
| 125 | `0x100000FA` | ordinary button | end character session |
`gmPanelUI::SetupChildren` reads enum property `0x10000029` from each
registered child. The installed main-panel LayoutDesc resolves the indicator
detail pages to Link Status `8`, Mini Game `9`, and Vitae `15` (Helpful and
Harmful remain `4` and `5`). They are not independent floating-window ids.
## Pseudocode
### Link indicator
@ -82,6 +98,46 @@ acdream records the last successfully decoded server datagram in
keeps socket/session facts out of the UI while preserving retail's thresholds
and cadence.
### Link Status detail page
```text
On shown:
register for time notices
pleaseRequestPing = true
Update()
On hidden:
unregister time notices
pleaseRequestPing = false
On time notice while visible:
if now >= nextUpdate:
nextUpdate = now + 5 seconds
Update()
Update:
replace text with localized connection explanation + 5/20-second legend
append 10-second packet-loss percentage with two decimal places
append ping in milliseconds, or "????" before a response
if pleaseRequestPing or now - lastPingRequest >= 120 seconds:
lastPingRequest = now
Event_RequestPing()
pleaseRequestPing = false
Event_RequestPing:
send F7B1, game-action sequence, opcode 0x01E9
// no payload
On empty 0x01EA PingResponse:
pingRoundTrip = monotonicNow - lastPingRequest
```
The network session owns the send timestamp and response sample. The retained
page owns only retail's display and refresh cadence. acdream's transport does
not yet expose the retail NAK/retransmission moving averages, so packet-loss
averaging remains the explicit AP-110 residual rather than being guessed from
ordinary packet sequence gaps.
### Helpful and harmful effects
```text
@ -109,6 +165,25 @@ On PlayerDesc or VitaeChanged:
SetState(Ghosted = 13)
```
The detail page uses the same modifier:
```text
lostPercent = 100 - int(vitae * 100)
if lostPercent <= 0:
show localized full-strength sentence
else:
vitaePool = player PropertyInt 129
deathLevel = player PropertyInt 139, falling back to Level (25)
threshold = int(((deathLevel ^ 2.5 * 2.5) + 20)
* vitae ^ 5 + 0.5)
remaining = threshold - vitaePool
show localized lost-percent sentence and remaining-XP sentence
```
The x87 powers omitted from the named pseudo-C output for
`VitaeCPPoolThreshold` were cross-checked against the official ACE
`Player_Xp.cs` port. The property ids and rounding expression agree.
### Burden / character information
```text
@ -136,6 +211,28 @@ 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.
The authored `gmMiniGameUI` root `0x1000016A` is mounted and registered as
main panel `9`, including its close behavior. It remains unreachable while the
indicator is Ghosted; board state and game actions remain with the unported
mini-game subsystem.
### Shared main-panel ownership
```text
For every toolbar or indicator detail page:
register page with gmPanelUI panel id
when shown:
hide the current child
move the requested child to the shared main-panel placement
show only the requested child
when a page with bool property 0x10000049 closes:
restore the deferred ordinary child
```
Helpful/Harmful, Link Status, Vitae, and the Mini Game shell therefore share
the same retained host position as Inventory, Character, and Magic. They do
not remember independent positions.
### End character session
```text
@ -159,8 +256,8 @@ CharacterLogOff and transport Disconnect packets.
`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.
- Official ACE was cross-checked for the empty PingResponse and the vitae-pool
threshold expression; the named retail client remains the behavior oracle.
- The Link Status and Vitae pages now use their authored DAT roots and localized
strings. AP-110 retains only packet-loss averaging and mini-game gameplay for
this slice; neither is replaced with an ad-hoc approximation.