Implement retail character management screen

This commit is contained in:
Erik 2026-08-14 20:29:19 +02:00
parent 5535d0adac
commit 6cfab727f1
19 changed files with 2435 additions and 6 deletions

View file

@ -157,6 +157,16 @@ window registration, plugin mounts, cursor feedback, layout persistence, and the
retained tick/draw/restore/dispose paths. Panel-specific construction must not
move back into `GameWindow.OnLoad`.
The graphical no-selector launch projects Runtime's sole
`RuntimeCharacterSelectionState` through the retained character-management root
resolved from DAT enum table 5 (`0x10000005` -> `0x21000004`, selected root
`0x1000039A`). App borrows the view and routes generation-capturing typed
commands; it owns no roster, highlight, operation, error, or lifecycle mirror.
The authored screen is a flat ListBox and buttons, with the shared retail dialog
catalog for confirmation, wait, and error presentation. It contains no viewport
or character preview. Explicit-selector graphical launches and no-window hosts
do not mount this presentation.
Magic follows the same boundary. Core `Spellbook` is the one learned/favorite/
desired/enchantment state projection; Core.Net owns exact manifest and live
message parsing; Runtime `RuntimeActionState.SpellCast` owns validated cast

View file

@ -0,0 +1,104 @@
# LA8 retained character-management UI evidence
Date: 2026-08-14
This note records the retail and installed-DAT evidence for Campaign LA slice
LA8, plus the exact ownership and presentation boundary implemented by the
slice. LA7b remains the authority for pre-world Runtime and wire behavior.
## Named-retail evidence
The implementation was derived from
`docs/research/named-retail/acclient_2013_pseudo_c.txt` and the corresponding
`acclient.h` definition before the screen was written.
- `DBObj::GetDIDByEnum` (`0x004153A0`) forwards to
`DBCache::GetDIDFromEnumStatic`; retail resolves the category/table mapping
before loading a LayoutDesc.
- `gmCharacterManagementUI::gmCharacterManagementUI` (`0x004EC8F0`) calls
`UIMainFramework::CreateAndAddRootElement(0x10000005, 0x1000039A)`, then
binds ListBox `0x1000039D`, Create `0x100003A0`, Enter `0x100003A2`, Delete
`0x1000039F`, and Restore `0x1000039E`.
- The verbatim header at `acclient.h:56545` declares exactly that ListBox,
those four button pointers, the selected row/guid, and four dialog contexts.
It declares no viewport, `gmCG3DView`, or preview owner.
- `RebuildCharacterList` (`0x004EC3A0`) creates each row through
`AddItemFromTemplateList`, retains character identity, displays pending
deletion in red, sorts by ordinal name, moves greyed entries to the tail,
and restores/falls back selection. LA8 preserves the already canonical LA7b
display order and identity instead of sorting an App copy.
- `SelectCharacter` (`0x004EC160`) and `UpdateButtons` (`0x004EC240`) establish
the highlight and button matrix: no or greyed selection disables Enter and
Delete; an active selection shows/enables Delete; a greyed selection hides
Delete and shows/enables Restore.
- `ListenToElementMessage` (`0x004ED5A0`) routes the list selection message,
button clicks, and row-template `0x100003A5` activation message `0x1A`.
Double-activating a row calls `EnterGame` (`0x004ED440`).
- `MakeDeleteCharacterConfirmationDialog` (`0x004ECCA0`) uses retail dialog
type 5 and compares the typed response with the localized DELETE response
case-insensitively. `MakePleaseWaitDialog` (`0x004ECED0`) and
`MakeEnteringWorldDialog` (`0x004ED090`) use the wait machinery. Error
presentation enters through `MakeErrorMessageDialog` (`0x004ECB10`). The
destructor (`0x004EC080`) closes every owned dialog context.
The shared dialog factory switch supplies catalog roots/classes used here:
message type 3 is root `0x24` / class `0x17`; confirmation-text-input type 5
is root `0x2C` / class `0x15`; the existing wait type 2 is root `0x31` /
class `0x19`. The message button is `0x26`. Type 5 uses field `0x2C`, accept
`0x2E`, reject `0x2F`, and result property `0x9C`.
## Installed-DAT proof
The permanent read-only acceptance probe is
`tests/AcDream.App.Tests/UI/Layout/CharacterManagementLiveDatTests.cs`. Run it
with `ACDREAM_PROBE_LIVE_MOUNT=1`; it reads the ordinary
`%USERPROFILE%/Documents/Asheron's Call` DAT set unless `ACDREAM_DAT_DIR`
overrides the location. It uses production `DatCollection`,
`RetailDataIdResolver`, and `LayoutImporter`; it does not write the DATs.
The installed September-2013 data proves:
- enum category/table 5 maps `0x10000005` to concrete LayoutDesc DID
**`0x21000004`**;
- selected root `0x1000039A` is 800 x 600 with eight authored children;
- the root itself authors image media `0x06007576`; that proves a retained
layout asset, not a separate render-loop background scene;
- its ListBox template is `{ 0x21000004, 0x100003A5 }`;
- the template is a 160 x 16 `UiButton`, font `0x40000009`, with Normal,
NormalRollover, NormalPressed, Highlight, HighlightRollover, and the authored
`0xFFFFFFFF` default state;
- the authored captions are Create Character, ENTER, DELETE, and RESTORE;
- neither the selected root nor any descendant is a `UiViewport`;
- enum-table-5 dialog key 2 maps to catalog DID `0x2100003C`, containing the
type-3 and type-5 roots/children above;
- string table `0x23000002` contains DELETE, Please Wait, Entering World, and
the delete-confirmation template. The template has the PLAYER variable and
resolves it into the selected character name.
## Ownership, composition, and lifecycle
`RetailUiRuntime` imports the exact enum-resolved root only for a graphical
launch with no explicit character selector. Its focused binding borrows
`IRuntimeCharacterSelectionView`; every highlight, enter, delete-request,
delete-confirm, restore, and cancel action crosses the existing deferred
adapter as a generation-capturing Runtime command. App retains no gameplay
mirror. Explicit-selector graphical launches keep their existing flow, and
headless does not compose this App presentation.
The controller instantiates the authored row template in Runtime display
order, projects red pending-delete rows and the exact button matrix, and opens
the shared retail dialogs. Delete wait survives the opcode-only acknowledgement
until the fresh roster arrives. Restore is fire-and-observe: a silent ACE
no-reply ends only when Runtime expires its correlation. Entering-world wait
opens before the existing synchronous Enter command; error, reset, reconnect,
missing/displaced adapter, and disposal close owned contexts without re-entrant
commands. A failed transient row-template import leaves the Runtime revision
unconsumed and retries on the next frame.
There is deliberately no 3D preview and no claimed character-select background
scene. The screen root remains neutral with respect to render-loop background
composition. LA11's user visual gate owns that unresolved visual choice, plus
the live local-ACE delete/restore check. Because Enter currently completes its
established ServerReady transaction synchronously, LA11 must also verify that
the entering-world wait is perceptible on the real frame path; this slice does
not introduce a second queue or lifecycle owner merely to force a paint.