acdream/docs/research/2026-08-14-la8-character-management-ui.md

7 KiB

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, then resizes it using signed integer division: max(listHeight / max(rosterCount, allowedSlots), listHeight / 10). Thus a 320-pixel list with five allowed slots uses 64-pixel rows, while rosters over ten clamp at 32 pixels. It 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. When the opt-in flag or installed data is absent, discovery records an explicit skip rather than adding a no-op pass to default suite totals.

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; retail's Please Wait opens before the synchronous restore command and closes immediately if that command rejects or throws. 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. Initial dialog-catalog, character root, and string misses likewise retry on later ticks without mounting a duplicate root or controller. Dialog presenter/catalog failures move their contexts to an internal retry ledger, so UI callbacks do not retain poisoned active/queued entries and the same context can appear after resource recovery. Priority contexts remain ahead of ordinary retries and preserve retail's nested preemption order when creation recovers. The mount coordinator owns a detached controller before attaching its root or running the first template-resolving tick; any partial failure disposes that exact controller before retry, so roots and handlers cannot accumulate.

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.