User reported that when they observed acdream's character through a second AC client running on a different account, the character rendered as a stationary purple haze (AC's "loading screen / portal space" indicator) instead of a normal avatar. The character was "in-world enough" to receive the CreateObject stream but never "in-world enough" for the server to flip its first-enter-world flag, push initial property updates / equipment overrides, or show the character to other clients in the area. Root cause: WorldSession.EnterWorld stopped after sending CharacterEnterWorld (0xF657). The handshake is supposed to continue with one more message — a GameAction(LoginComplete) — that ACE's GameActionLoginComplete handler interprets as "client has exited portal space, mark FirstEnterWorldDone, push property updates, make the character visible to others." Wire layout (confirmed via references/ACE/Source/ACE.Server/Network/GameAction/GameActionPacket.cs and .../Actions/GameActionLoginComplete.cs): u32 game-message opcode = 0xF7B1 (GameAction) u32 sequence = 0 (ACE ignores; TODO comment in source) u32 GameActionType opc = 0x000000A1 (LoginComplete) Send happens immediately after CharacterEnterWorld and just before flipping the WorldSession state to InWorld. acdream has no portal- space transition animation, so we can claim "loading complete" the moment we've sent the EnterWorld message — the dat-side world is already loaded by then. 1 new test (97 Core.Net total). 220 tests green overall. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25 lines
872 B
C#
25 lines
872 B
C#
using AcDream.Core.Net.Messages;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Net.Tests.Messages;
|
|
|
|
public class GameActionLoginCompleteTests
|
|
{
|
|
[Fact]
|
|
public void Build_ProducesExactly12Bytes_WithCorrectOpcodes()
|
|
{
|
|
var body = GameActionLoginComplete.Build();
|
|
|
|
// 4 bytes GameAction opcode + 4 bytes sequence + 4 bytes action type.
|
|
Assert.Equal(12, body.Length);
|
|
|
|
// Little-endian decode.
|
|
uint gameActionOpcode = (uint)(body[0] | (body[1] << 8) | (body[2] << 16) | (body[3] << 24));
|
|
uint sequence = (uint)(body[4] | (body[5] << 8) | (body[6] << 16) | (body[7] << 24));
|
|
uint actionType = (uint)(body[8] | (body[9] << 8) | (body[10] << 16) | (body[11] << 24));
|
|
|
|
Assert.Equal(0xF7B1u, gameActionOpcode);
|
|
Assert.Equal(0u, sequence);
|
|
Assert.Equal(0x000000A1u, actionType);
|
|
}
|
|
}
|