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

@ -61,6 +61,12 @@ back to inert generic DAT elements.
`gmUIElement_EffectsIndicator`. The retained controller consequently had no
live bindings for session packet age, Vitae, or the player load ratio and did
not dispatch Character Information or the shared logout confirmation path.
The strip and its actions are now ported. Link Status (panel 8), both effect
pages (4/5), Vitae (15), and the authored Mini Game shell (9) are registered as
children of the same retained main-panel owner as Inventory/Character/Magic,
so switching pages preserves one canonical position. Link ping RTT and Vitae
recovery XP are live; transport packet-loss averaging and Mini Game gameplay
remain the explicitly narrowed AP-110 residual.
**Files:** `src/AcDream.App/UI/Layout/IndicatorBarController.cs`;
`src/AcDream.App/UI/Layout/DatWidgetFactory.cs`;
@ -74,6 +80,8 @@ quality follows the 5/20/40-second thresholds and bad-link flash cadence;
helpful, harmful, Vitae, and burden react to live state; the burden icon opens
Character Information; effects open their matching panels; crossed swords use
the shared confirmation dialog and close gracefully only after acceptance.
Every non-ghosted detail icon opens its authored page in the shared main-panel
position; closing a restore-previous page returns to the deferred ordinary page.
---

View file

@ -207,7 +207,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
| ~~AP-107~~ | **RETIRED 2026-07-11 (Wave 3.3 / #197)** — typed `OfferPrimaryClick` returns `NotActive`, `ConsumedSuccess`, or `ConsumedRejected`; every retained item surface plus radar/world offers active target mode before local selection/open/use fallback. Rejections are consumed and cannot drift selection. | `src/AcDream.App/UI/ItemInteractionController.cs`; inventory/paperdoll/toolbar/radar/world call sites | — | — | `UIElement_ItemList::HandleTargetedUseLeftClick @ 0x004E24D0` |
| AP-108 | Paperdoll/AutoWield still omit full `AutoWieldIsLegal`/dual-wield rules, double-click examine/drag from the doll, body-part selection lighting, and retail's synchronous `" - cannot unwield the %s"` failure suffix (the current send seam reports rejection asynchronously). **Primary replacement retired from this row 2026-07-14:** inventory activation and paperdoll drops share the confirmed blocker transaction, preserve explicit slot intent, relocate an already-worn item to an explicitly selected compatible slot, and emit retail's successful move-to-backpack status. **Aetheria retired 2026-07-13.** | `src/AcDream.App/UI/Layout/PaperdollController.cs`; `src/AcDream.App/UI/AutoWieldController.cs` | Basic equip slots, Aetheria, and live doll work; primary weapon, incompatible shield, and mismatched ammo blockers sequence through server-confirmed dequip→wield in both peace and war | Remaining illegal/off-hand cases, asynchronous dequip rejection wording, doll examine/drag, and selection lighting still differ functionally | `CPlayerSystem::AutoWield @ 0x00560A60`; `ACCWeenieObject::BlocksUseOfShield @ 0x0055D3E0`; `gmPaperDollUI @ 0x004A3590..0x004A5F90` |
| AP-109 | Character Titles page is inert and live displayed-title/luminance state is absent | `src/AcDream.App/UI/Layout/CharacterStatController.cs`; `CharacterSheetProvider.cs` | Attributes/skills core output is user-accepted | Titles cannot be selected/displayed and level-200 luminance fields are missing | `gmCharacterTitleUI @ 0x0049A610`; `gmStatManagementUI::UpdateExperience @ 0x004F0A70` |
| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/examine/floating chat, quests/map/vitae/options/smartbox, vendor/trade/salvage/tinkering, and D.6 nameplates/floaters | D.5/D.6 roadmap; retained layout registration set | Basic combat plus M3 spellbook/component/effects/favorite-spell surfaces now cover the active melee/missile/magic loops; later waves land each remaining surface against authoritative state | Large portions of retail gameplay still have no production UI outside the current combat/magic/inventory/character loops | Named `gm*UI::PostInit` methods and LayoutDesc catalog |
| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/examine/floating chat, quests/map/options/smartbox, vendor/trade/salvage/tinkering, mini-game gameplay, Link Status NAK/retransmission packet-loss averaging, and D.6 nameplates/floaters | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.Core.Net/LinkStatusSnapshot.cs`; D.5/D.6 roadmap | Basic combat plus M3 spellbook/component/effects/favorite-spell/Link/Vitae surfaces now cover the active melee/missile/magic loops; the Link page has exact ping RTT but reports the current transport's zero default until its missing reliability statistics land | Large portions of retail gameplay still have no production UI; real packet loss is displayed as 0.00% instead of retail's moving average | Named `gm*UI::PostInit` methods; `CLinkStatusAverages::GetAveragePacketLoss @ 0x00546610`; LayoutDesc catalog |
| ~~AP-111~~ | **RETIRED 2026-07-11 (M2 held-object parenting)** — equipped hand items are no longer omitted from the render world. CreateObject now preserves Placement/Parent/position timestamp bootstrap; live `0xF749` ParentEvent is parsed with retail sequence freshness; a focused render controller resolves `Setup.HoldingLocations`, applies the child's placement frame, and recomposes the separate child entity after every parent animation tick. Pickup retains the weenie's visual metadata for a later wield. | `src/AcDream.Core.Net/Messages/{CreateObject,ParentEvent}.cs`; `src/AcDream.Core/Meshing/EquippedChildAttachment.cs`; `src/AcDream.App/Rendering/EquippedChildRenderController.cs` | — | — | `ClientCombatSystem::GetDefaultCombatMode @ 0x0056B310`; `SmartBox::HandleParentEvent @ 0x004535D0`; `CPhysicsObj::set_parent @ 0x00515A90`; `CPhysicsObj::UpdateChild @ 0x00512D50` |
| AP-112 | The basic combat bar ports visibility, height selection, desired-power slider, exact 1.0/0.8-second charge, ready-stance gating, request/release, `MaybeStopCompletely`, server-response queueing, and auto-repeat, but still omits `StartAttackRequest`'s `FinishJump` call and exact trained-Recklessness visibility semantics (IA-20 keeps the dark range as the accepted baseline) | `src/AcDream.App/Combat/CombatAttackController.cs`; `src/AcDream.App/UI/Layout/CombatUiController.cs` | The shared player movement owner now performs retail's server-control-gated full stop and movement report before an attack build; the remaining seams require the jump owner and a distinct Recklessness treatment | Starting an attack while charging a jump may not finish that jump exactly when retail does; trained/untrained Recklessness presentation is identical | `ClientCombatSystem::StartAttackRequest @ 0x0056C040`; `CommandInterpreter::MaybeStopCompletely @ 0x006B3B90`; `gmCombatUI::ListenToElementMessage @ 0x004CC430` |
| AP-113 | Invalid lifestone-command arguments display the local text `Usage: /lifestone`; retail definitely emits a local usage/error line but Binary Ninja misidentifies the referenced wide-string address, so its exact wording is not yet recovered | `src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs`; `RetailClientCommandCatalog.cs` | The behavior boundary is exact (handled locally, no chat and no game action); only a low-impact diagnostic sentence differs | `/ls now` can show different wording/color from retail while still refusing the invalid request correctly | `ClientCommunicationSystem::DoLifestone @ 0x0056FC70` |

View file

@ -1,6 +1,6 @@
# acdream — strategic roadmap
**Status:** Living document. Updated 2026-07-17. **M3 active; automated implementation complete, visual gates pending.** M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels, the complete authored seven-control gameplay indicator strip, and shared `gmPanelUI` switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate.
**Status:** Living document. Updated 2026-07-17. **M3 active; automated implementation complete, visual gates pending.** M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels, the complete authored seven-control gameplay indicator strip and its Link/Vitae detail pages, and shared `gmPanelUI` switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate.
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
---
@ -22,7 +22,9 @@ active enchantment, then recall through the DAT-driven portal presentation.
equipped caster endowment, scoped retail keys, spellbook/component book,
positive/negative active-enchantment panels, the seven-control link/effect/
Vitae/burden/mini-game/session indicator strip, and the retail
one-active-main-panel lifecycle. Connected-gate
one-active-main-panel lifecycle. Link Status, Helpful/Harmful, Vitae, and
the inactive Mini Game shell now occupy that same canonical main-panel
position; Link RTT and Vitae recovery XP are live. Connected-gate
correction now also pins type-12 spellbook tabs, retail zero text margins,
and `PassToChildren` Closed/Open chrome through real-DAT fixtures.
3. **Pending user visual gate:** connected projectile/self-buff casts plus the

View file

@ -714,6 +714,11 @@ the burden/effect/logout actions use the shared panel/dialog owners. Main-panel
visibility now flows through retail's one-active `gmPanelUI` owner. M3 remains
active only for the connected single-client magic
UI/casting visual gate and the final two-client portal observer gate.
The 2026-07-17 indicator follow-up also mounts the authored Link Status, Vitae,
and Mini Game detail roots as children of that same main-panel owner. Helpful,
Harmful, Link, and Vitae therefore replace Inventory/Character/Magic in one
canonical window position. Link uses retail's payload-free ping/RTT cadence;
Vitae uses PropertyInt 129/139 and the retail XP-pool threshold formula.
The first connected gate exposed and corrected two retained-import defects:
spellbook tabs are authored stateful text controls (not buttons), and retail
text starts with zero margins. Real-DAT fixtures now pin spellbook binding,

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.