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 toDBCache::GetDIDFromEnumStatic; retail resolves the category/table mapping before loading a LayoutDesc.gmCharacterManagementUI::gmCharacterManagementUI(0x004EC8F0) callsUIMainFramework::CreateAndAddRootElement(0x10000005, 0x1000039A), then binds ListBox0x1000039D, Create0x100003A0, Enter0x100003A2, Delete0x1000039F, and Restore0x1000039E.- The verbatim header at
acclient.h:56545declares 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 throughAddItemFromTemplateList, 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) andUpdateButtons(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-template0x100003A5activation message0x1A. Double-activating a row callsEnterGame(0x004ED440).MakeDeleteCharacterConfirmationDialog(0x004ECCA0) uses retail dialog type 5 and compares the typed response with the localized DELETE response case-insensitively.MakePleaseWaitDialog(0x004ECED0) andMakeEnteringWorldDialog(0x004ED090) use the wait machinery. Error presentation enters throughMakeErrorMessageDialog(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
0x10000005to concrete LayoutDesc DID0x21000004; - selected root
0x1000039Ais 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, font0x40000009, with Normal, NormalRollover, NormalPressed, Highlight, HighlightRollover, and the authored0xFFFFFFFFdefault 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
0x23000002contains 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.