From ada95756ce307695993311255abcccf94ad60e33 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 28 Mar 2026 11:09:36 -0700 Subject: [PATCH] fix: don't exit warmup until terrain under player is loaded Added terrain readiness check to the warmup exit condition: the loading screen won't drop until getHeightAt(playerPos) returns a valid height, ensuring the ground exists under the player's feet before spawning. Also increased warmup hard cap from 15s to 25s to give terrain more time to load in cities like Stormwind with dense WMO/M2 assets. Equipment re-emit chain confirmed working: items resolve 3-4 seconds after spawn and equipment is re-applied with valid displayIds. --- src/core/application.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/core/application.cpp b/src/core/application.cpp index 044b7498..b9cc1367 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -5230,7 +5230,7 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float // (character clothed, NPCs placed, game objects loaded) when the screen drops. { const float kMinWarmupSeconds = 2.0f; // minimum time to drain network packets - const float kMaxWarmupSeconds = 15.0f; // hard cap to avoid infinite stall + const float kMaxWarmupSeconds = 25.0f; // hard cap to avoid infinite stall const auto warmupStart = std::chrono::high_resolution_clock::now(); // Track consecutive idle iterations (all queues empty) to detect convergence int idleIterations = 0; @@ -5315,10 +5315,22 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float idleIterations = 0; } - // Exit when: (min time passed AND queues drained for several iterations) OR hard cap - bool readyToExit = (elapsed >= kMinWarmupSeconds && idleIterations >= kIdleThreshold); + // Don't exit warmup until the terrain tile under the player's feet is loaded. + // This prevents falling through the world on spawn. + bool terrainReady = true; + if (renderer && renderer->getTerrainManager()) { + auto* tm = renderer->getTerrainManager(); + float px = renderer->getCharacterPosition().x; + float py = renderer->getCharacterPosition().y; + terrainReady = tm->getHeightAt(px, py).has_value(); + } + + // Exit when: (min time passed AND queues drained AND terrain ready) OR hard cap + bool readyToExit = (elapsed >= kMinWarmupSeconds && idleIterations >= kIdleThreshold && terrainReady); if (readyToExit || elapsed >= kMaxWarmupSeconds) { - if (elapsed >= kMaxWarmupSeconds) { + if (elapsed >= kMaxWarmupSeconds && !terrainReady) { + LOG_WARNING("Warmup hit hard cap (", kMaxWarmupSeconds, "s), terrain NOT ready — may fall through world"); + } else if (elapsed >= kMaxWarmupSeconds) { LOG_WARNING("Warmup hit hard cap (", kMaxWarmupSeconds, "s), entering world with pending work"); } break;