# Retail dialog factory and confirmation lifecycle Date: 2026-07-13 ## Scope This note pins the reusable retail dialog architecture needed by acdream's confirmation call sites. It ports the factory/context/queue lifecycle and the type-1 `ConfirmationDialog`. The other six catalog types are identified here so the factory can accept their presenters later, but their type-specific data contracts are outside this slice. ## Named retail sources - `Dialog::UpdatePendingDialogDisplay @ 0x00476930` - `Dialog::UpdateDialogText @ 0x00476A50` - `Dialog::SetData @ 0x00476BE0` - `DialogFactory::DialogDone @ 0x004773C0` - `DialogFactory::UpdatePendingDialogDisplay_ @ 0x00477590` - `DialogFactory::Reset @ 0x00477950` - `DialogFactory::CreateDialog_ @ 0x00477AD0` - `DialogFactory::OpenNextDialog @ 0x00477D30` - `DialogFactory::MakeDialog @ 0x00477E90` - `DialogFactory::CloseDialog @ 0x00478160` - `DialogFactory::MakeCallbackDialogInCurrentUI @ 0x00478430` - `ConfirmationDialog::SetData @ 0x004764D0` - `ConfirmationDialog::ListenToElementMessage @ 0x00476670` - `ConfirmationDialog::CancelDialog @ 0x00476770` - `ClientCommunicationSystem::DoDie @ 0x00580050` - `ClientCommunicationSystem::DieDialogCallback @ 0x0057BA70` - `ClientUISystem::UsageCallback @ 0x00565B20` - `ClientUISystem::UsageConfirmation_PKAltar @ 0x00566420` - `ClientUISystem::UsageConfirmation_NPKAltar @ 0x00566610` - `ClientUISystem::UsageConfirmation_VolatileRare @ 0x00566800` - `ClientUISystem::Handle_Character__ConfirmationRequest @ 0x005640A0` - `gmGamePlayUI::MakeGameplayConfirmationDialog @ 0x004EB890` - `gmGamePlayUI::RecvNotice_CloseDialog @ 0x004EA4F0` - `gmGamePlayUI::RecvNotice_AbortConfirmationRequest @ 0x004E9D70` Verbatim `DialogInfo` layout: `docs/research/named-retail/acclient.h`, struct 5922. Protocol payloads were independently cross-checked against ACE and ACClientLib during the command-family port; see `docs/research/2026-07-13-retail-client-command-families-pseudocode.md`. ## Property contract ```text 0x8D boolean priority/preempt-current flag 0x8E integer dialog type 0x90 StringInfo optional accept caption 0x91 StringInfo optional reject caption 0x92 boolean confirmation result 0xAC boolean when true, Dialog::SetData sets UIElement attribute 0x40 0xC3 integer queue key; default 2, value 1 is nonqueued 0xC5 StringInfo main dialog text 0x1000003D instance item/object id carried by item confirmations ``` Dialog type to catalog-root/type mapping in `CreateDialog_`: ```text 1 -> root 0x15, element type 0x13, ConfirmationDialog 2 -> root 0x31, element type 0x19, WaitDialog 3 -> root 0x24, element type 0x17, MessageDialog 4 -> root 0x28, element type 0x18, TextInputDialog 5 -> root 0x2C, element type 0x15, ConfirmationTextInputDialog 6 -> root 0x1B, element type 0x16, MenuDialog 7 -> root 0x1F, element type 0x14, ConfirmationMenuDialog ``` ## Factory pseudocode ```text make_dialog(parent, input_data): context = ++global_context info = DialogInfo(copy(input_data), pointer=null, parent, context) priority = input_data.bool(0x8D, false) queue_key = input_data.uint(0xC3, 2) if queue_key == 1: append info to active_nonqueued create_dialog(info, queue_key) return context if no active dialog for queue_key: create_dialog(info, queue_key) else if not priority: pending[queue_key].push_back(info) update_pending_displays() else: current = active[queue_key] remove current root from parent without completing it pending[queue_key].push_front(current) create_dialog(info, queue_key) return context create_dialog(info, queue_key): store info as active nonqueued or active[queue_key] read type from property 0x8E create a FRESH root element from shared catalog enum (2, 5) assign context to the Dialog dialog.SetData(info.data) bring dialog to front update_pending_displays() broadcast OpenDialog(context) update_pending_display(dialog, pending_count): resolve child 0x33 and its text child 0x34 once child_0x33.SetState(pending_count == 0 ? 0x19 : 0x18) // retail does not synthesize numeric count text close_dialog(context): if context is active nonqueued: dialog_done(info) remove it return if context is an active queued dialog: remove active[queue_key] dialog_done(info) open_next_dialog(queue_key) return if context is pending: dialog_done(info) remove it from pending update_pending_displays() dialog_done(info): if callback exists for info.context: callback(dialog.data) remove callback broadcast CloseDialog(info.context, dialog.data) remove the root element from its parent dialog.context = 0 open_next_dialog(queue_key): if pending[queue_key] has an entry: info = pop_front() create_dialog(info, queue_key) delete an empty pending queue ``` `Reset` runs `dialog_done` for active and pending dialogs before clearing all factory collections and callbacks. ## Confirmation presenter pseudocode ```text confirmation.SetData(data): base.SetData(data) // copies collection; 0xC5 updates child 0x3E if data has 0x90: set child 0x17 caption if data has 0x91: set child 0x19 caption on button message: if child is not 0x17 or 0x19: delegate to base data[0x92] = (child == 0x17) DialogFactory.CloseDialog(context) cancel: data[0x92] = false DialogFactory.CloseDialog(context) ``` The catalog/root/class are reused; the live widget instance is not. Retail creates a fresh root for every displayed `DialogInfo`, which is required for simultaneous nonqueued dialogs and independent queue groups. ## Integration ownership ```text /die: data = { 0x8E=1, 0xC5=retail message } MakeCallbackDialogInCurrentUI(data, DieDialogCallback) callback sends suicide only when 0x92 is true item confirmation: data = { 0x8E=1, 0xC5=message, 0x1000003D=item id } MakeCallbackDialogInCurrentUI(data, UsageCallback) callback sends Use only when 0x92 is true server gameplay confirmation: gameplay UI stores server type/context data = { 0x8E=1, 0xC5=message } context = MakeDialog(gameplay UI, data) // no direct callback DialogFactory's CloseDialog notice reaches gmGamePlayUI gmGamePlayUI reads 0x92 and sends ConfirmationResponse(type, server context, result) matching ConfirmationDone closes the context through DialogFactory ```