Harden packet framing/logging and checkpoint current workspace state

This commit is contained in:
Kelsi 2026-02-12 02:27:59 -08:00
parent 4b48fcdab2
commit 615efd01b7
9 changed files with 290 additions and 147 deletions

View file

@ -29,6 +29,42 @@
namespace wowee {
namespace game {
namespace {
const char* worldStateName(WorldState state) {
switch (state) {
case WorldState::DISCONNECTED: return "DISCONNECTED";
case WorldState::CONNECTING: return "CONNECTING";
case WorldState::CONNECTED: return "CONNECTED";
case WorldState::CHALLENGE_RECEIVED: return "CHALLENGE_RECEIVED";
case WorldState::AUTH_SENT: return "AUTH_SENT";
case WorldState::AUTHENTICATED: return "AUTHENTICATED";
case WorldState::READY: return "READY";
case WorldState::CHAR_LIST_REQUESTED: return "CHAR_LIST_REQUESTED";
case WorldState::CHAR_LIST_RECEIVED: return "CHAR_LIST_RECEIVED";
case WorldState::ENTERING_WORLD: return "ENTERING_WORLD";
case WorldState::IN_WORLD: return "IN_WORLD";
case WorldState::FAILED: return "FAILED";
}
return "UNKNOWN";
}
bool isAuthCharPipelineOpcode(uint16_t opcode) {
switch (opcode) {
case static_cast<uint16_t>(Opcode::SMSG_AUTH_CHALLENGE):
case static_cast<uint16_t>(Opcode::SMSG_AUTH_RESPONSE):
case static_cast<uint16_t>(Opcode::SMSG_CLIENTCACHE_VERSION):
case static_cast<uint16_t>(Opcode::SMSG_TUTORIAL_FLAGS):
case static_cast<uint16_t>(Opcode::SMSG_WARDEN_DATA):
case static_cast<uint16_t>(Opcode::SMSG_CHAR_ENUM):
case static_cast<uint16_t>(Opcode::SMSG_CHAR_CREATE):
case static_cast<uint16_t>(Opcode::SMSG_CHAR_DELETE):
return true;
default:
return false;
}
}
} // namespace
GameHandler::GameHandler() {
LOG_DEBUG("GameHandler created");
@ -171,7 +207,7 @@ void GameHandler::update(float deltaTime) {
socketTime += std::chrono::duration<float, std::milli>(socketEnd - socketStart).count();
// Post-gate visibility: determine whether server goes silent or closes after Warden requirement.
if (wardenGateSeen_ && socket) {
if (wardenGateSeen_ && socket && socket->isConnected()) {
wardenGateElapsed_ += deltaTime;
if (wardenGateElapsed_ >= wardenGateNextStatusLog_) {
LOG_INFO("Warden gate status: elapsed=", wardenGateElapsed_,
@ -504,6 +540,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
if (wardenGateSeen_ && opcode != static_cast<uint16_t>(Opcode::SMSG_WARDEN_DATA)) {
++wardenPacketsAfterGate_;
}
if (isAuthCharPipelineOpcode(opcode)) {
LOG_INFO("AUTH/CHAR RX opcode=0x", std::hex, opcode, std::dec,
" state=", worldStateName(state),
" size=", packet.getSize());
}
LOG_DEBUG("Received world packet: opcode=0x", std::hex, opcode, std::dec,
" size=", packet.getSize(), " bytes");
@ -516,7 +557,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
if (state == WorldState::CONNECTED) {
handleAuthChallenge(packet);
} else {
LOG_WARNING("Unexpected SMSG_AUTH_CHALLENGE in state: ", (int)state);
LOG_WARNING("Unexpected SMSG_AUTH_CHALLENGE in state: ", worldStateName(state));
}
break;
@ -524,7 +565,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
if (state == WorldState::AUTH_SENT) {
handleAuthResponse(packet);
} else {
LOG_WARNING("Unexpected SMSG_AUTH_RESPONSE in state: ", (int)state);
LOG_WARNING("Unexpected SMSG_AUTH_RESPONSE in state: ", worldStateName(state));
}
break;
@ -546,7 +587,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
if (state == WorldState::CHAR_LIST_REQUESTED) {
handleCharEnum(packet);
} else {
LOG_WARNING("Unexpected SMSG_CHAR_ENUM in state: ", (int)state);
LOG_WARNING("Unexpected SMSG_CHAR_ENUM in state: ", worldStateName(state));
}
break;
@ -554,7 +595,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
if (state == WorldState::ENTERING_WORLD || state == WorldState::IN_WORLD) {
handleLoginVerifyWorld(packet);
} else {
LOG_WARNING("Unexpected SMSG_LOGIN_VERIFY_WORLD in state: ", (int)state);
LOG_WARNING("Unexpected SMSG_LOGIN_VERIFY_WORLD in state: ", worldStateName(state));
}
break;
@ -1433,7 +1474,7 @@ void GameHandler::requestCharacterList() {
if (state != WorldState::READY && state != WorldState::AUTHENTICATED &&
state != WorldState::CHAR_LIST_RECEIVED) {
LOG_WARNING("Cannot request character list in state: ", (int)state);
LOG_WARNING("Cannot request character list in state: ", worldStateName(state));
return;
}
@ -1507,7 +1548,7 @@ void GameHandler::createCharacter(const CharCreateData& data) {
if (state != WorldState::CHAR_LIST_RECEIVED) {
std::string msg = "Character list not ready yet. Wait for SMSG_CHAR_ENUM.";
LOG_WARNING("Blocking CMSG_CHAR_CREATE in state=", static_cast<int>(state),
LOG_WARNING("Blocking CMSG_CHAR_CREATE in state=", worldStateName(state),
" (awaiting CHAR_LIST_RECEIVED)");
if (charCreateCallback_) {
charCreateCallback_(false, msg);

View file

@ -5,6 +5,7 @@
#include "pipeline/dbc_loader.hpp"
#include "pipeline/asset_manager.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtx/quaternion.hpp>
#include <cmath>
#include <iostream>
@ -43,7 +44,7 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId,
transport.guid = guid;
transport.wmoInstanceId = wmoInstanceId;
transport.pathId = pathId;
transport.allowBootstrapVelocity = true;
transport.allowBootstrapVelocity = false;
// CRITICAL: Set basePosition from spawn position and t=0 offset
// For stationary paths (1 waypoint), just use spawn position directly
@ -79,6 +80,8 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId,
transport.clientAnimationReverse = false;
transport.serverYaw = 0.0f;
transport.hasServerYaw = false;
transport.serverYawFlipped180 = false;
transport.serverYawAlignmentScore = 0;
transport.lastServerUpdate = 0.0f;
transport.serverUpdateCount = 0;
transport.serverLinearVelocity = glm::vec3(0.0f);
@ -254,16 +257,7 @@ void TransportManager::updateTransportMovement(ActiveTransport& transport, float
}
pathTimeMs = transport.localClockMs % path.durationMs;
} else {
// Server-driven transport without clock sync:
// keep server-authoritative and dead-reckon from last known velocity.
const float ageSec = elapsedTime_ - transport.lastServerUpdate;
constexpr float kMaxExtrapolationSec = 30.0f;
if (transport.hasServerVelocity && ageSec > 0.0f && ageSec <= kMaxExtrapolationSec) {
const float blend = glm::clamp(1.0f - (ageSec / kMaxExtrapolationSec), 0.0f, 1.0f);
transport.position += transport.serverLinearVelocity * (deltaTime * blend);
}
// Strict server-authoritative mode: do not guess movement between server snapshots.
updateTransformMatrices(transport);
if (wmoRenderer_) {
wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
@ -279,11 +273,17 @@ void TransportManager::updateTransportMovement(ActiveTransport& transport, float
constexpr float kMinFallbackZOffset = -2.0f;
pathOffset.z = glm::max(pathOffset.z, kMinFallbackZOffset);
}
if (!transport.useClientAnimation && !transport.hasServerClock) {
constexpr float kMinFallbackZOffset = -2.0f;
constexpr float kMaxFallbackZOffset = 8.0f;
pathOffset.z = glm::clamp(pathOffset.z, kMinFallbackZOffset, kMaxFallbackZOffset);
}
transport.position = transport.basePosition + pathOffset;
// Use server yaw if available (authoritative), otherwise compute from tangent
if (transport.hasServerYaw) {
transport.rotation = glm::angleAxis(transport.serverYaw, glm::vec3(0.0f, 0.0f, 1.0f));
float effectiveYaw = transport.serverYaw + (transport.serverYawFlipped180 ? glm::pi<float>() : 0.0f);
transport.rotation = glm::angleAxis(effectiveYaw, glm::vec3(0.0f, 0.0f, 1.0f));
} else {
transport.rotation = orientationFromTangent(path, pathTimeMs);
}
@ -560,7 +560,8 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
transport->position = position;
transport->serverYaw = orientation;
transport->hasServerYaw = true;
transport->rotation = glm::angleAxis(transport->serverYaw, glm::vec3(0.0f, 0.0f, 1.0f));
float effectiveYaw = transport->serverYaw + (transport->serverYawFlipped180 ? glm::pi<float>() : 0.0f);
transport->rotation = glm::angleAxis(effectiveYaw, glm::vec3(0.0f, 0.0f, 1.0f));
if (hadPrevUpdate) {
const float dt = elapsedTime_ - prevUpdateTime;
@ -570,6 +571,35 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
constexpr float kMinAuthoritativeSpeed = 0.15f;
constexpr float kMaxSpeed = 60.0f;
if (speed >= kMinAuthoritativeSpeed) {
// Auto-detect 180-degree yaw mismatch by comparing heading to movement direction.
// Some transports appear to report yaw opposite their actual travel direction.
glm::vec2 horizontalV(v.x, v.y);
float hLen = glm::length(horizontalV);
if (hLen > 0.2f) {
horizontalV /= hLen;
glm::vec2 heading(std::cos(transport->serverYaw), std::sin(transport->serverYaw));
float alignDot = glm::dot(heading, horizontalV);
if (alignDot < -0.35f) {
transport->serverYawAlignmentScore = std::max(transport->serverYawAlignmentScore - 1, -12);
} else if (alignDot > 0.35f) {
transport->serverYawAlignmentScore = std::min(transport->serverYawAlignmentScore + 1, 12);
}
if (!transport->serverYawFlipped180 && transport->serverYawAlignmentScore <= -4) {
transport->serverYawFlipped180 = true;
LOG_INFO("Transport 0x", std::hex, guid, std::dec,
" enabled 180-degree yaw correction (alignScore=",
transport->serverYawAlignmentScore, ")");
} else if (transport->serverYawFlipped180 &&
transport->serverYawAlignmentScore >= 4) {
transport->serverYawFlipped180 = false;
LOG_INFO("Transport 0x", std::hex, guid, std::dec,
" disabled 180-degree yaw correction (alignScore=",
transport->serverYawAlignmentScore, ")");
}
}
if (speed > kMaxSpeed) {
v *= (kMaxSpeed / speed);
}
@ -577,12 +607,36 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
transport->serverLinearVelocity = v;
transport->serverAngularVelocity = 0.0f;
transport->hasServerVelocity = true;
// Re-apply potentially corrected yaw this frame after alignment check.
effectiveYaw = transport->serverYaw + (transport->serverYawFlipped180 ? glm::pi<float>() : 0.0f);
transport->rotation = glm::angleAxis(effectiveYaw, glm::vec3(0.0f, 0.0f, 1.0f));
}
}
} else {
// Seed fallback path phase from nearest waypoint to the first authoritative sample.
auto pathIt2 = paths_.find(transport->pathId);
if (pathIt2 != paths_.end()) {
const auto& path = pathIt2->second;
if (!path.points.empty() && path.durationMs > 0) {
glm::vec3 local = position - transport->basePosition;
size_t bestIdx = 0;
float bestDistSq = std::numeric_limits<float>::max();
for (size_t i = 0; i < path.points.size(); ++i) {
glm::vec3 d = path.points[i].pos - local;
float distSq = glm::dot(d, d);
if (distSq < bestDistSq) {
bestDistSq = distSq;
bestIdx = i;
}
}
transport->localClockMs = path.points[bestIdx].tMs % path.durationMs;
}
}
// Bootstrap velocity from mapped DBC path on first authoritative sample.
// This avoids "stalled at dock" when server sends sparse transport snapshots.
auto pathIt2 = paths_.find(transport->pathId);
pathIt2 = paths_.find(transport->pathId);
if (transport->allowBootstrapVelocity && pathIt2 != paths_.end()) {
const auto& path = pathIt2->second;
if (path.points.size() >= 2 && path.durationMs > 0) {