2V3-a: walk with the keyboard through the ported player chain

PythonPlayer, PythonPlayerInput{,Keyboard,Mouse}, PythonPlayerEventHandler,
PythonPlayerSkill, PythonPlayerModule and GameType are verbatim copies; the
`player` stub module and the pending PythonPlayer/GameType stand-ins are gone.
Unported callees (CPythonSkill, CItemData, CItemManager, CPythonItem, ...) are
pending stubs. 64-bit fixes: emotion icon handles use Py_BuildPointer, and
PyDict_Next takes Py_ssize_t.

The actor did not move because GrannyUpdateModelMatrix passed the model matrix
through. Walk/run motions use constant root-motion extraction
(AccumulationFlags=AccumulationExtracted, LoopTranslation e.g. (0,-300,0) for
the warrior run, same as the .msa Accumulation). libgr2 now reads both fields
from TrackGroups[0], and the runtime moves by LoopTranslation over each
control's local-clock span, blended by control weight. VDA extraction is not
implemented.

port.login_flow presses DIK_UP; the actor walks more than 100 cm, and the fake
server parses CG packets and sees CG_MOVE FUNC_MOVE, then FUNC_WAIT at the new
position. port.login_live against the real server walked 132 cm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 23:41:31 +09:00
co-authored by Claude Opus 5.5
parent 157f7c3bb4
commit ff0983d3c8
39 changed files with 10170 additions and 2039 deletions
+42 -5
View File
@@ -509,9 +509,46 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
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)
;
return true;
// Then hold the connection until the test stops us, reading what the game phase sends the way
// CInputProcessor::Process frames it (static size or TDynamicSizePacketHeader, then the sequence
// byte). A CG_MOVE (input_main.cpp CInputMain::Move) becomes "<name>:move <func> <arg> <rot> <x> <y>".
for (;;)
{
std::uint8_t packet[1024] = {};
if (c.Read(packet, 1) != 1)
return true;
const std::uint8_t header = packet[0];
size_t size = static_cast<size_t>(packet_size_cg(header));
if (is_dynamic_cg(header) || header == HDR_CG_SYNC_POSITION)
{
if (c.Read(packet + 1, sizeof(DynHead) - 1) != 1)
return true;
DynHead head;
std::memcpy(&head, packet, sizeof(head));
size = head.size;
if (size < sizeof(DynHead) || size > sizeof(packet) ||
(size > sizeof(DynHead) && c.Read(packet + sizeof(DynHead), size - sizeof(DynHead)) != 1))
return Fail(name + ": dynamic CG packet " + std::to_string(header) + " in the game phase");
}
else if (size == 0 || size > sizeof(packet))
return Fail(name + ": unknown CG header " + std::to_string(header) + " in the game phase");
else if (size > 1 && c.Read(packet + 1, size - 1) != 1)
return true;
if (is_sequence_cg(header))
{
std::uint8_t sequence = 0;
if (c.Read(&sequence, 1) != 1)
return true;
if (sequence != SEQUENCE_TABLE[c.sequence])
return Fail(name + ": SEQUENCE mismatch on CG header " + std::to_string(header));
c.sequence = (c.sequence + 1) % SEQUENCE_TABLE_SIZE;
}
if (header == HDR_CG_MOVE)
{
CGMove move;
std::memcpy(&move, packet, sizeof(move));
Event(name + ":move " + std::to_string(move.func) + " " + std::to_string(move.arg) + " " +
std::to_string(move.rot) + " " + std::to_string(move.x) + " " + std::to_string(move.y));
}
}
}