fix(ui): complete retail indicator detail panels
Port the authored effect row template, remaining-time and selection details, synchronize the full gmPanelUI child geometry, and route the burden indicator to Character Information panel 3. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
a96767ba6d
commit
d1d603105f
24 changed files with 3696 additions and 147 deletions
|
|
@ -61,10 +61,14 @@ 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
|
||||
The strip and its actions are now ported. Character Information (panel 3), Link
|
||||
Status (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 size and
|
||||
position. Helpful/Harmful use retail's authored 300x32 icon/name/time rows,
|
||||
selection states, `SELECT A SPELL` prompt, and selected spell description.
|
||||
The burden icon routes to Character Information rather than Attributes/Skills.
|
||||
Link ping RTT and Vitae
|
||||
recovery XP are live; transport packet-loss averaging and Mini Game gameplay
|
||||
remain the explicitly narrowed AP-110 residual.
|
||||
|
||||
|
|
@ -81,7 +85,7 @@ 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.
|
||||
geometry; closing a restore-previous page returns to the deferred ordinary page.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| IA-9 | One unified camera matrix for terrain — retail's separate `LScape::update_viewpoint` landscape viewpoint does not exist | `src/AcDream.App/Rendering/TerrainModernRenderer.cs:266` | Phase W T4.2: with one matrix everywhere, viewpoint-desync bugs are unrepresentable — the unification IS the correctness argument | Anything retail derives from the landcell-relative viewpoint (float precision at extreme coords, viewpoint-keyed state) has no analogue; a future port expecting it silently reads the camera | `LScape::update_viewpoint`; `LScape::draw` 0x00506330 |
|
||||
| IA-10 | Transparent groups sorted back-to-front per GROUP by first-instance position (no within-group sort) vs retail per-poly BSP-order draw | `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs:1364` (comparer :1662) | One MDI call per pass requires group-granularity ordering; per-poly sorting is incompatible with instanced multi-draw; works when group instances are spatially coherent | Spatially spread or interleaved transparent groups composite in the wrong order — popping / wrong see-through layering as the camera moves | retail per-poly BSP-order transparent draw (D3DPolyRender / PView::DrawCells) |
|
||||
| IA-11 | Tier-1 cross-frame batch-classification cache for static entities (retail re-walks part arrays every frame) | `src/AcDream.App/Rendering/Wb/EntityClassificationCache.cs:12` | Issue #53 perf tier; invariants documented (keys = EntityId + OWNING-landblock hint post-**#119** fix `2163308`; invalidation at despawn/LB-unload; mutation audit 2026-05-10) | Key collision or missed invalidation serves one entity another's batches — session-sticky wrong meshes (the #119 broken-stairs/water-barrel symptom) | retail per-frame part-array classification (no cache) |
|
||||
| IA-12 | UI toolkit mirrors retail behavior from research docs, not a byte-port — keystone.dll is outside decomp coverage; observed constants embedded (drag 3 px, tooltip 1000 ms). Synthetic wrapper borders/whole-window drag regions use the exact DAT Type-2/Type-9 control cursors. `gmPanelUI` primary children are independently imported retained frames rather than one physical parent, but `RetailPanelUiController` owns their one canonical placement and exclusive child lifecycle | `src/AcDream.App/UI/README.md:3`; `src/AcDream.App/UI/CursorFeedbackController.cs`; `src/AcDream.App/UI/Layout/RetailPanelUiController.cs` | keystone.dll has no PDB/decomp; semantics are reconstructed from the retail UI deep-dives and named client methods plus production LayoutDesc media. Separate child wrappers preserve each LayoutDesc's content/resize policy while typed handle synchronization gives Inventory/Character/Magic the same persistent parent position | Edge-case input semantics can differ silently; if retail relies on parent-size anchoring across a panel switch, separate child extents may resize differently even though placement and visibility match | `UIElementManager::CheckCursor` 0x0045ABF0; `UIElement_Resizebar::StartMouseResizing` 0x0046B7E0; `UIElement_Dragbar::StartMouseMoving` 0x0046C760; `gmPanelUI::SetupChildren` 0x004BC9E0; docs/research/retail-ui/04-input-events.md |
|
||||
| IA-12 | UI toolkit mirrors retail behavior from research docs, not a byte-port — keystone.dll is outside decomp coverage; observed constants embedded (drag 3 px, tooltip 1000 ms). Synthetic wrapper borders/whole-window drag regions use the exact DAT Type-2/Type-9 control cursors. `gmPanelUI` children are independently imported retained frames rather than one physical parent, but `RetailPanelUiController` owns their one canonical geometry and exclusive child lifecycle | `src/AcDream.App/UI/README.md:3`; `src/AcDream.App/UI/CursorFeedbackController.cs`; `src/AcDream.App/UI/Layout/RetailPanelUiController.cs` | keystone.dll has no PDB/decomp; semantics are reconstructed from retail UI deep-dives, named client methods, and production LayoutDesc media. Separate child wrappers preserve each LayoutDesc's content tree while typed move/resize synchronization gives all registered toolbar/detail children the same persistent parent rectangle | Edge-case low-level input semantics can differ silently even though outer geometry, visibility, and restore-previous ownership match | `UIElementManager::CheckCursor` 0x0045ABF0; `UIElement_Resizebar::StartMouseResizing` 0x0046B7E0; `UIElement_Dragbar::StartMouseMoving` 0x0046C760; `gmPanelUI::SetupChildren` 0x004BC9E0; docs/research/retail-ui/04-input-events.md |
|
||||
| IA-13 | GameEventType registry deliberately omits event types retail ignores; unknown events fall through unhandled | `src/AcDream.Core.Net/Messages/GameEventType.cs:11` | Retail also ignores them — dropping matches retail by construction | If the "retail ignores X" judgment is wrong for any opcode (or a server mod uses one), the event is silently dropped with no diagnostic pointing at the omission | retail GameEvent dispatch (ignored-event set) |
|
||||
| IA-14 | Rendering + dat-handling base is WorldBuilder's tested port, not a fresh retail-decomp port (Phase N.4/O design stance) | `docs/architecture/worldbuilder-inventory.md` (code at `src/AcDream.{Core,App}/Rendering/Wb/`) | WB visually verified on the AC world, MIT, same stack; known WB↔retail deltas resolved case-by-case — terrain split kept retail `FSplitNESW` (**#51**, pinned by `SplitFormulaDivergenceTest`), scenery drift accepted (AP-31) | A WB-upstream divergence not yet caught ships silently as "our" behavior; guard = the inventory doc's 🟢/🔴 split + per-formula divergence tests | retail decomp per algorithm; `tests/.../SplitFormulaDivergenceTest.cs` |
|
||||
| IA-15 | D.2b gameplay UI is our own `UiHost`/`UiRoot` retained tree, not a byte-port of Keystone. `RetailUiRuntime` owns the production import/mount graph; `RetailWindowManager`/typed handles centralize registry, raise, focus/capture cleanup, lifecycle events, reverse-order grouped controller teardown, removable Silk input subscriptions, schema-v2 per-character/per-resolution automatic layouts, and portable named `saveui/loadui` profiles; `RetailWindowFrame` is the single production/Studio mount contract for imported-chrome and shared-wrapper windows. Production LayoutDesc imports include vitals `0x2100006C`, chat `0x21000006`, toolbar `0x21000016`, character `0x2100002E`, inventory `0x21000023` plus mounted `0x21000024/22/21`, dialog catalog `0x2100003C`, and radar `0x21000074`. The dialog context/queue/callback lifecycle is now a named-client port; only its retained rendering remains under this Keystone adaptation. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/RetailWindowManager.cs`; `src/AcDream.App/UI/RetainedPanelControllerGroup.cs`; `src/AcDream.App/UI/UiHost.cs`; `src/AcDream.App/UI/RetailWindowLayoutPersistence.cs`; `src/AcDream.App/UI/Layout/RetailWindowFrame.cs`; `src/AcDream.App/UI/Layout/RetailDialogFactory.cs`; `src/AcDream.App/UI/Layout/RetailConfirmationDialogView.cs`; `src/AcDream.App/UI/Layout/LayoutImporter.cs`; binding supply in `GameWindow.cs` | Keystone has no matching PDB/decomp, so we preserve its observable ElementDesc/state/input behavior from DAT, named client call sites, and live evidence while using modern retained ownership. Real RenderSurfaces and imported element geometry remain the visual oracle. | Persistence and low-level widget rendering are behaviorally reconstructed from retail semantics rather than a Keystone byte-port; lifecycle edge cases remain constrained by conformance tests | Production LayoutDesc objects; `DialogFactory @ 0x004773C0..0x00478470`; `docs/research/2026-07-13-retail-dialog-factory-pseudocode.md`; Keystone behavior notes in `docs/research/retail-ui/` |
|
||||
|
|
|
|||
|
|
@ -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 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.
|
||||
**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 with authored rows and selected-spell details, the complete seven-control gameplay indicator strip and its Character Information/Link/Vitae detail pages, and shared `gmPanelUI` geometry/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.
|
||||
|
||||
---
|
||||
|
|
@ -23,8 +23,11 @@ active enchantment, then recall through the DAT-driven portal presentation.
|
|||
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. 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
|
||||
Character Information, and the inactive Mini Game shell now occupy that same
|
||||
canonical main-panel size and position; Link RTT and Vitae recovery XP are
|
||||
live. Effects now use the exact icon/name/time row template, selection state,
|
||||
and `SELECT A SPELL`/description field; burden opens Character Information.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -714,11 +714,14 @@ 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 2026-07-17 indicator follow-up also mounts authored Character Information,
|
||||
Link Status, Vitae, and Mini Game detail roots as children of that same
|
||||
main-panel owner. Helpful, Harmful, Character Information, Link, and Vitae
|
||||
therefore replace Inventory/Character/Magic in one canonical window rectangle.
|
||||
Helpful/Harmful instantiate retail's DAT icon/name/time row and selected-spell
|
||||
information presentation. The burden icon sends panel 3 (Character Information),
|
||||
not panel 11 (Attributes/Skills). 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,
|
||||
|
|
|
|||
|
|
@ -569,6 +569,8 @@ negative root 0x10000121 (inherits 0x10000122)
|
|||
shared effects template 0x10000122
|
||||
effect list 0x10000123
|
||||
information text 0x10000126
|
||||
effect row template 0x10000128 (300 x 32)
|
||||
row icon / name / duration 0x10000129 / 0x1000012A / 0x1000012B
|
||||
effect UI type property 0x1000000C
|
||||
```
|
||||
|
||||
|
|
@ -622,6 +624,16 @@ an active token stores its spell ID, so every layer of that spell highlights
|
|||
together and shows the shared name/description; clicking either layer again
|
||||
clears the selection.
|
||||
|
||||
`InfoRegion::InfoRegion @ 0x004F1450` creates each row with
|
||||
`UIElement_ListBox::AddItemFromTemplateList(template 0)`. In installed retail
|
||||
data the icon is x=0 width 32, name is x=37 width 188, and duration is x=225
|
||||
width 50. `EffectInfoRegion::Update @ 0x004F1C00` formats finite time as
|
||||
`m:ss` or `h:mm:ss`; a negative/permanent duration leaves the field empty.
|
||||
`gmEffectsUI::UpdateSelection @ 0x004B7F90` displays localized
|
||||
`ID_Effects_Info_SelectASpell` while nothing is selected, otherwise spell name
|
||||
plus two newlines plus description. Normal rows use state 1 and every row with
|
||||
the selected spell ID uses Highlight state 6.
|
||||
|
||||
## Portal presentation boundary
|
||||
|
||||
Recall and portal presentation remains DAT-driven. `/ls` starts the retail
|
||||
|
|
|
|||
|
|
@ -223,15 +223,44 @@ 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
|
||||
apply the shared main-panel rectangle to the requested child
|
||||
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.
|
||||
Character Information, Helpful/Harmful, Link Status, Vitae, and the Mini Game
|
||||
shell therefore share the same retained host size and position as Inventory,
|
||||
Character/Skills, and Magic. They do not remember independent rectangles.
|
||||
|
||||
The burden/backpack indicator sends panel id `3`, whose production child is
|
||||
root `0x10000183` under LayoutDesc `0x2100006E`. Panel `11` is the separate
|
||||
Attributes/Skills child and is not the burden indicator destination.
|
||||
|
||||
### Helpful and harmful effect rows
|
||||
|
||||
```text
|
||||
gmEffectsUI::RebuildList @ 0x004B8350:
|
||||
sort visible enchantment tokens alphabetically by spell name
|
||||
AddItemFromTemplateList(template 0)
|
||||
|
||||
EffectInfoRegion row template 0x10000128 (LayoutDesc 0x2100001B):
|
||||
icon 0x10000129 at x=0, 32 x 32
|
||||
name 0x1000012A at x=37, 188 x 32
|
||||
time left 0x1000012B at x=225, 50 x 32
|
||||
|
||||
gmEffectsUI::UpdateSelection @ 0x004B7F90:
|
||||
no selected spell -> display ID_Effects_Info_SelectASpell
|
||||
selected spell -> display name + "\n\n" + description
|
||||
matching rows use Highlight state 6; other rows use Normal state 1
|
||||
|
||||
gmEffectsUI::SetSelectedSpell @ 0x004B8290:
|
||||
clicking the selected spell again clears selection
|
||||
```
|
||||
|
||||
The installed local.dat resolves `ID_Effects_Info_SelectASpell` to
|
||||
`SELECT A SPELL`. Remaining finite durations use `m:ss` below one hour and
|
||||
`h:mm:ss` at one hour or more.
|
||||
|
||||
### End character session
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Retail shared main-panel placement — pseudocode
|
||||
# Retail shared main-panel geometry — pseudocode
|
||||
|
||||
**Date:** 2026-07-17
|
||||
**Symptom:** moving Inventory and then opening Character/Skills or Magic opens
|
||||
|
|
@ -73,10 +73,11 @@ SetPanelVisibility(panelId, show):
|
|||
requested.child.SetVisible(false)
|
||||
```
|
||||
|
||||
The decisive placement fact is structural: Inventory, Character/Skills, and
|
||||
Magic are children of one `gmPanelUI`. The switch changes child visibility;
|
||||
there is no child move and no per-panel top-level position. Dragging the parent
|
||||
therefore moves the one position subsequently used by every child.
|
||||
The decisive geometry fact is structural: Inventory, Character Information,
|
||||
Character/Skills, Magic, Effects, and the other detail pages are children of
|
||||
one `gmPanelUI`. The switch changes child visibility; there is no child move,
|
||||
resize, or per-panel top-level rectangle. Moving or resizing the parent
|
||||
therefore changes the one rectangle subsequently used by every child.
|
||||
|
||||
## Resize follow-up
|
||||
|
||||
|
|
@ -86,9 +87,12 @@ The production end-of-retail LayoutDesc supplies the missing geometry oracle:
|
|||
LayoutDesc 0x2100006E
|
||||
gmPanelUI host 0x100005FE 310 x 372
|
||||
content parent 0x10000180 300 x 362, anchored on all edges
|
||||
Inventory child 0x1000018B 300 x 362, anchored on all edges
|
||||
Character child 0x1000018E 300 x 362, anchored on all edges
|
||||
Magic child 0x10000190 300 x 362, anchored on all edges
|
||||
Character Information 0x10000183 panel 3, 300 x 362
|
||||
Helpful Effects 0x10000184 panel 4, 300 x 362
|
||||
Harmful Effects 0x10000185 panel 5, 300 x 362
|
||||
Inventory child 0x1000018B panel 7, 300 x 362
|
||||
Character/Skills child 0x1000018E panel 11, 300 x 362
|
||||
Magic child 0x10000190 panel 13, 300 x 362
|
||||
|
||||
top-center 0x1000065C Type 2 Dragbar, cursor 0x06006119
|
||||
bottom-center 0x10000660 Type 9 Resizebar, cursor 0x06005E66
|
||||
|
|
@ -102,7 +106,8 @@ properties `0x3C..0x3F`. There is no retail 760-pixel Character limit and no
|
|||
maximum captured from the host's original screen position.
|
||||
|
||||
acdream retains independent wrappers under IA-12, so the faithful adaptation
|
||||
is to give each primary wrapper the same bottom-only vertical resize policy,
|
||||
is to give each registered child wrapper the same bottom-only vertical resize
|
||||
policy,
|
||||
leave its maximum unbounded by an authored substitute, and calculate the
|
||||
available desktop extent from its current position during the resize. The
|
||||
viewport remains the physical limit; an old static maximum must not become a
|
||||
|
|
@ -113,31 +118,31 @@ smaller limit after the shared panel moves.
|
|||
acdream imports these child LayoutDesc roots independently, so each currently
|
||||
has its own retained wrapper. `RetailPanelUiController` already ports retail's
|
||||
one-active-child and restore-previous lifecycle. Complete that logical-parent
|
||||
ownership by adding one canonical placement for the primary toolbar children
|
||||
(Inventory `7`, Character `11`, Magic `13`):
|
||||
ownership by adding one canonical rectangle for all registered main-panel
|
||||
children, including Character Information `3`, Helpful/Harmful `4`/`5`,
|
||||
Inventory `7`, Character/Skills `11`, and Magic `13`:
|
||||
|
||||
```text
|
||||
on primary child frame moved:
|
||||
canonicalPosition = moved frame position
|
||||
move every other primary child frame to canonicalPosition
|
||||
on registered child frame moved or resized:
|
||||
canonicalGeometry = frame left, top, width, height
|
||||
apply canonicalGeometry to every hidden sibling
|
||||
|
||||
before showing a primary child:
|
||||
if canonicalPosition exists:
|
||||
move requested child frame to canonicalPosition
|
||||
before showing a registered child:
|
||||
if canonicalGeometry exists:
|
||||
apply canonicalGeometry to requested child frame
|
||||
else:
|
||||
canonicalPosition = requested child frame position
|
||||
canonicalGeometry = requested child frame geometry
|
||||
|
||||
before hiding the active primary child:
|
||||
capture its current position as canonicalPosition
|
||||
before hiding the active child:
|
||||
capture its current geometry as canonicalGeometry
|
||||
```
|
||||
|
||||
The controller owns this synchronization and subscribes to typed
|
||||
`RetailWindowHandle.Moved` events. Toolbar commands, keyboard commands,
|
||||
restore-previous effects, persistence restore, and direct window dragging all
|
||||
therefore pass through the same owner. Panel-authored size/resize policy stays
|
||||
with the active child, but the three primary wrappers apply the shared host's
|
||||
same vertical resize contract; only placement is synchronized between the
|
||||
independent frames.
|
||||
`RetailWindowHandle.Moved` and `Resized` events. Toolbar, indicator, and
|
||||
keyboard commands, restore-previous effects, persistence restore, and direct
|
||||
window dragging all therefore pass through the same owner. Each wrapper
|
||||
retains its own imported content tree, but its outer rectangle and bottom-only
|
||||
resize contract mirror the shared retail host.
|
||||
|
||||
ACE contains no client UI implementation, and the in-tree WorldBuilder
|
||||
reference contains no `gmPanelUI` port, so neither supplies a competing
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue