port 2V2-b: GamePhase dispatch consumes the Entergame burst

PythonNetworkStreamPhaseGame/PhaseGameActor/PhaseGameItem/Command,
NetworkActorManager and PythonChat/PythonChatModule are ported verbatim and
dispatched at runtime; the 2V0 chat stub module is removed and initChat()
runs before initnet() as in 40250. InstanceBase, PythonCharacterManager and
GameLib ActorInstance/RaceData/RaceManager compile but stay TODO until
2V2-c/d wire them.

Units not ported yet get their singletons from PythonBoot's GameSingletons
(CPythonApplication member order) and platform/pending stand-ins generated
by platform_stub.py pending; const char* stand-ins return "".

port.login_flow's fake server now sends the CInputLogin::Entergame order
(actor insert, NPC positions, PHASE_GAME, land list, time, channel, greet
notice, ping) and a TraceError observer asserts no unknown packet header.
port.login_live passes against the real server for 10 s of game phase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 18:20:57 +09:00
co-authored by Claude Opus 5.5
parent d16fabf61c
commit 62b6e23bfc
76 changed files with 21087 additions and 753 deletions
@@ -440,6 +440,72 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
return Fail(name + ": CG_ENTERGAME: " + error);
Event(name + ":entergame");
// CInputLogin::Entergame order: ch->Show inserts the main actor while the client is still in
// the loading phase (CHARACTER::EncodeInsertPacket = ADD + ADDITIONAL_INFO, then UpdatePacket),
// then SendNPCPosition, PHASE_GAME, SendLandList, TIME, CHANNEL and the greet notice.
GCCharacterAdd actor = zeroed<GCCharacterAdd>();
actor.header = HDR_GC_CHARACTER_ADD;
actor.vid = kMainVID;
actor.x = kX;
actor.y = kY;
actor.type = 0; // CHAR_TYPE_PC
actor.race = 0; // warrior male
actor.moving_speed = 100;
actor.attack_speed = 100;
GCCharAddInfo info = zeroed<GCCharAddInfo>();
info.header = HDR_GC_CHAR_ADDITIONAL_INFO;
info.vid = kMainVID;
std::strncpy(info.name, kCharacterName, sizeof(info.name) - 1);
info.empire = 1;
info.level = 10;
GCCharacterUpdate update = zeroed<GCCharacterUpdate>();
update.header = HDR_GC_CHARACTER_UPDATE;
update.vid = kMainVID;
update.moving_speed = 100;
update.attack_speed = 100;
if (!c.Send(actor) || !c.Send(info) || !c.Send(update))
return Fail(name + ": send main actor");
// packet_npc_position = DynHead + count, then TNPCPosition entries.
NPCPosition34 npc = zeroed<NPCPosition34>();
npc.type = 1; // CHAR_TYPE_NPC
std::strncpy(npc.name, "Npc", sizeof(npc.name) - 1);
npc.x = kX + 500;
npc.y = kY + 500;
DynHead npc_head = {HDR_GC_NPC_POSITION, static_cast<std::uint16_t>(sizeof(DynHead) + sizeof(GCNPCPositionHead) + sizeof(npc))};
GCNPCPositionHead npc_count = {1};
if (!c.Send(npc_head) || !c.Send(npc_count) || !c.Send(npc))
return Fail(name + ": send NPC positions");
if (!c.Phase(PHASE_GAME))
return Fail(name + ": send PHASE_GAME");
LandPacketElement24 land = zeroed<LandPacketElement24>();
land.id = 1;
land.x = kX + 1000;
land.y = kY + 1000;
land.width = 2000;
land.height = 2000;
DynHead land_head = {HDR_GC_LAND_LIST, static_cast<std::uint16_t>(sizeof(DynHead) + sizeof(land))};
GCTime game_time = {HDR_GC_TIME, static_cast<std::int32_t>(now_ms())};
GCChannel channel = {HDR_GC_CHANNEL, 1};
if (!c.Send(land_head) || !c.Send(land) || !c.Send(game_time) || !c.Send(channel))
return Fail(name + ": send land/time/channel");
// SendGreetMessage: a CHAT_TYPE_NOTICE line with no speaker.
const char greet[] = "Welcome";
GCChatHead chat = {HDR_GC_CHAT, static_cast<std::uint16_t>(sizeof(GCChatHead) + sizeof(greet)), 2 /* CHAT_TYPE_NOTICE */, 0, 1};
if (!c.Send(chat) || !c.Send(greet, sizeof(greet)))
return Fail(name + ": send greet notice");
Event(name + ":burst_sent");
// A ping after the burst proves that GamePhase consumed every packet above and can reply.
Blank ping = {HDR_GC_PING};
Blank pong = {};
if (!c.Send(ping) || read_packet(c, HDR_CG_PONG, &pong, sizeof(pong), &error) != 1)
return Fail(name + ": CG_PONG after the entergame burst: " + error);
Event(name + ":burst_pong");
// Hold the connection until the test stops us.
std::uint8_t byte = 0;
while (!m_stop && c.Read(&byte, 1) == 1)