# Retail graceful character logout ## Oracle - `Proto_UI::LogOffCharacter` `0x00546A20` - `CPlayerSystem::RequestLogOff` `0x00562DD0` - inbound login-message dispatch at `0x0055C963` - `CPlayerSystem::ExecuteLogOff` `0x0055D780` - `ClientNet::ExitWorldDisconnect` `0x00541E00` - `ClientNet::LogOffServer` `0x00543EF0` - `SharedNet::SendOptionalHeader` `0x00543160` - `CPlayerSystem::LogOffCharacter` `0x00563520` Cross-checks: - ACE `CharacterHandler.CharacterLogOff` begins asynchronous player removal. - ACE `Session.SendFinalLogOffMessages` sends the opcode-only `CharacterLogOff` confirmation only after the player no longer owns a landblock, then returns the session to `AuthConnected`. - Holtburger currently sends opcode-only and disconnects immediately. Its own comment records that AC normally waits for the server response. That shortcut is not the retail oracle and was the source of acdream's reconnect race. ## Pseudocode ```text LogOffCharacter(force): save player module if force: ExecuteLogOff() return if no interaction is pending: RequestLogOff() else: print "Logging off..." remember that logout must begin after the interaction RequestLogOff(): print "Logging off..." send_to_logon([0xF653, active_player_id]) logOffRequested = true logOffRequestTime = now + 3 seconds if player is PK: logOffRequestTime += 20 seconds on inbound login message 0xF653: ExecuteLogOff() ExecuteLogOff(): clear player-login state for every live ReceiverData connection: send a cleartext, empty DISCONNECT optional header use sequence 0 plus that receiver's network id and iteration disconnect the world connection clear the active player id ``` ## acdream close-only adaptation acdream does not yet return to character selection when its native window is closed. It retains retail's important ordering: send the eight-byte request, continue receiving until the server's opcode-only confirmation arrives, then send the transport `DISCONNECT` and release the socket. World callbacks are not dispatched while shutdown drains the raw receive queue because their App owners may already be tearing down. A bounded 35-second wait covers retail's additional 20-second PK logout delay and is only a transport safety limit; the normal path advances on the authoritative confirmation, not elapsed time. The connected gate separately observes ACE accepting the transport header before opening a replacement process; ACE removes the authenticated session on its world-manager tick, after the UDP sender has already completed.