fix(net): conform character entry and session shutdown

This commit is contained in:
Erik 2026-07-21 10:33:03 +02:00
parent bacc7e45a9
commit aea957f845
12 changed files with 1016 additions and 95 deletions

View file

@ -0,0 +1,157 @@
# Retail live-session lifecycle pseudocode
**Scope:** GameWindow Slice 3 character-list, character-entry, and transport-
disconnect conformance corrections.
## Named-retail oracle
- `CharacterIdentity::UnPack @ 0x004FE880`
- `CharacterSet::UnPack @ 0x004FE340`
- `CharacterSet::GetGreyedOutFor @ 0x004FDFA0`
- `gmCharacterManagementUI::EnterGame @ 0x004ED440`
- `gmCharGenMainUI::Update @ 0x004E8460`
- `CPlayerSystem::Handle_Login__CharacterSet @ 0x0055F6D0`
- `Proto_UI::SendEnterWorldRequest @ 0x00546A00`
- `CPlayerSystem::Handle_Character__EnterGame_ServerReady @ 0x0055E4F0`
- `Proto_UI::SendEnterWorld @ 0x00546BC0`
- `CPlayerSystem::LogOnCharacter @ 0x0055F890`
- `CPlayerSystem::UseTime @ 0x00563110`
- `Proto_UI::LogOffCharacter @ 0x00546A20`
- `CPlayerSystem::RequestLogOff @ 0x00562DD0`
- inbound F653 dispatch `@ 0x0055C963`
- `CPlayerSystem::ExecuteLogOff @ 0x0055D780`
- `ClientNet::ExitWorldDisconnect @ 0x00541E00`
- `ClientNet::LogOffServer @ 0x00543EF0`
- `SharedNet::SendOptionalHeader @ 0x00543160`
The verbatim layouts are `CharacterIdentity` and `CharacterSet` in
`docs/research/named-retail/acclient.h` (types 3206 and 3209).
## CharacterSet wire decode
```text
CharacterIdentity.UnPack(cursor):
identity.guid = read_u32(cursor)
identity.name = read_PString(cursor)
identity.secondsGreyedOut = read_u32(cursor)
align cursor to 4 bytes
CharacterSet.UnPack(cursor):
result.status = read_u32(cursor)
activeCount = read_u32(cursor)
result.active = empty
repeat activeCount times:
result.active.append(CharacterIdentity.UnPack(cursor))
deletedCount = read_u32(cursor)
result.deleted = empty
repeat deletedCount times:
result.deleted.append(CharacterIdentity.UnPack(cursor))
result.numAllowedCharacters = read_u32(cursor)
result.account = read_PString(cursor)
result.useTurbineChat = read_u32(cursor)
result.hasThroneOfDestiny = read_u32(cursor)
```
The two values previously described as "leading/trailing padding" are the
retail status and deleted-character count. ACE currently writes zero for both,
which hid the schema error in acdream and in Holtburger's current reader.
Cross-checks:
- ACE `GameMessageCharacterList` writes the same active identity records,
allowed-slot count, canonical `Session.Account`, and booleans, but currently
emits status/deleted-count as zero and no deleted records.
- Holtburger `CharacterListData` agrees on active records and the tail, but
likewise labels both zero values as padding. The named retail `CharacterSet`
layout and `UnPack` control flow win where the references differ.
## Retail selection and acdream unattended entry
```text
gmCharacterManagementUI.EnterGame():
playerSystem = GetPlayerSystem()
if playerSystem exists
and selectedGuid != 0
and CharacterSet.GetGreyedOutFor(
CharacterSet.GetSlot(selectedGuid)) <= 0:
show entering-world dialog
playerSystem.LogOnCharacter(selectedGuid)
```
Retail ordinarily requires an explicit selected active character. The only
automatic retail path found is `gmCharGenMainUI::Update`: after character
creation it walks the active list, skips entries greyed for a positive number
of seconds, matches the newly-created name case-insensitively, and logs that
GUID on.
acdream does not yet have the retained character-management screen. Its
unattended auto-entry is therefore an explicit adaptation with a narrow rule:
```text
for each identity in active wire order:
if identity.guid != 0 and identity.secondsGreyedOut == 0:
select this active-list index
stop
if none matched:
do not enter a character
```
Deleted entries are never candidates. This preserves the established "first
available character" behavior without treating a pending-deletion character
as available.
## Canonical account used by EnterWorld
```text
CPlayerSystem.LogOnCharacter(selectedGuid), when ready:
selectedGuid = persistentData.selectedCharacter
account = playerSystem.account // populated from CharacterSet.account
Proto_UI.SendEnterWorld(account, selectedGuid)
ClientNet.EnterWorld()
```
The account string in the final F657 message comes from the server-returned
`CharacterSet.account`, not from the spelling typed into the startup login
form. acdream must therefore make `WorldSession.EnterWorld` consume
`Characters.AccountName` internally.
## Logout and negotiated transport disconnect
```text
ClientNet.LogOffServer():
packet = make optional-header packet(flags = Disconnect)
for each negotiated ReceiverData:
SharedNet.SendOptionalHeader(packet, receiver.address, receiver)
logOffSent = true
logonReceiverId = 0
ExitWorldDisconnect()
```
The in-world character path remains:
```text
send F653 + active character GUID
wait for server's opcode-only F653
send negotiated transport Disconnect
release transport
```
ACE's `GameMessageCharacterLogOff` confirms the server form is exactly four
bytes. Its `NetworkSession` terminates a session when it receives a packet with
the transport Disconnect flag.
The transport Disconnect is connection-scoped, not character-state-scoped.
Once acdream has accepted ConnectRequest receiver id/iteration, disposal sends
it whether the session is still selecting a character, entering the world, in
world, or failed later. F653 remains restricted to an active in-world
character. A pre-negotiation failure has no receiver identity and only closes
the local socket.
## Deliberately unchanged registered gaps
- UN-6: the fixed 200 ms ConnectResponse delay remains registered.
- TS-28: LoginComplete readiness remains a later protocol slice.
- AD-44: closing exits rather than returning to a retained character screen.