From 97c95941f4abef30be3dda94d14c9357eba0942f Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Sun, 12 Apr 2026 20:02:50 +0300 Subject: [PATCH] feat(world-map): remove kVOffset hack, ZMP hover, textured player arrow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove the -0.15 vertical offset (kVOffset) from coordinate_projection, coordinate_display, and zone_highlight_layer; continent UV math is now identical to zone UV math - Switch world_map_facade aspect ratio to MAP_W/MAP_H (1002×668) and crop the FBO image with MAP_U_MAX/MAP_V_MAX instead of stretching the full 1024×768 FBO - Account for ImGui title bar height (GetFrameHeight) in window sizing and zone highlight screen-space rect coordinates - Add ZMP 128×128 grid pixel-accurate hover detection in zone_highlight_layer; falls back to AABB when ZMP data is unavailable - Upgrade PlayerMarkerLayer with full Vulkan lifecycle (initialize, clearTexture, destructor); loads MinimapArrow.blp and renders a rotated 32×32 textured quad via AddImageQuad; red triangle retained as fallback - Expose arrowRotation_ / arrowDS_ accessors on Minimap; clean up arrow DS and texture in Minimap::shutdown() - Wire PlayerMarkerLayer::initialize() into WorldMapFacade::initialize() - Update coordinate-projection test: continent and zone UV are now equal Signed-off-by: Pavel Okhlopkov --- include/rendering/minimap.hpp | 9 ++ .../world_map/layers/player_marker_layer.hpp | 19 +++ src/rendering/minimap.cpp | 6 + .../world_map/coordinate_projection.cpp | 6 +- .../world_map/layers/coordinate_display.cpp | 3 - .../world_map/layers/player_marker_layer.cpp | 118 +++++++++++++++--- .../world_map/layers/zone_highlight_layer.cpp | 57 +++++++-- src/rendering/world_map/world_map_facade.cpp | 50 +++++--- .../test_world_map_coordinate_projection.cpp | 4 +- 9 files changed, 218 insertions(+), 54 deletions(-) diff --git a/include/rendering/minimap.hpp b/include/rendering/minimap.hpp index 906f4666..5b838d99 100644 --- a/include/rendering/minimap.hpp +++ b/include/rendering/minimap.hpp @@ -56,6 +56,9 @@ public: void setOpacity(float opacity) { opacity_ = opacity; } + float getArrowRotation() const { return arrowRotation_; } + VkDescriptorSet getArrowDS() const { return arrowDS_; } + // Public accessors for WorldMap VkTexture* getOrLoadTileTexture(int tileX, int tileY); void ensureTRSParsed() { if (!trsParsed) parseTRS(); } @@ -121,6 +124,12 @@ private: // Tile tracking int lastCenterTileX = -1; int lastCenterTileY = -1; + + // Player arrow texture (MinimapArrow.blp) + std::unique_ptr arrowTexture_; + VkDescriptorSet arrowDS_ = VK_NULL_HANDLE; + bool arrowLoadAttempted_ = false; + float arrowRotation_ = 0.0f; }; } // namespace rendering diff --git a/include/rendering/world_map/layers/player_marker_layer.hpp b/include/rendering/world_map/layers/player_marker_layer.hpp index 4b006c68..23ba48bb 100644 --- a/include/rendering/world_map/layers/player_marker_layer.hpp +++ b/include/rendering/world_map/layers/player_marker_layer.hpp @@ -1,14 +1,33 @@ // player_marker_layer.hpp — Directional player arrow on the world map. #pragma once #include "rendering/world_map/overlay_renderer.hpp" +#include "rendering/vk_texture.hpp" +#include +#include namespace wowee { namespace rendering { +class VkContext; +} +namespace pipeline { class AssetManager; } +namespace rendering { namespace world_map { class PlayerMarkerLayer : public IOverlayLayer { public: + ~PlayerMarkerLayer() override; + void initialize(VkContext* ctx, pipeline::AssetManager* am); + void clearTexture(); void render(const LayerContext& ctx) override; + +private: + void ensureTexture(); + + VkContext* vkCtx_ = nullptr; + pipeline::AssetManager* assetManager_ = nullptr; + std::unique_ptr texture_; + VkDescriptorSet imguiDS_ = VK_NULL_HANDLE; + bool loadAttempted_ = false; }; } // namespace world_map diff --git a/src/rendering/minimap.cpp b/src/rendering/minimap.cpp index 6e9fbb05..4ae580ba 100644 --- a/src/rendering/minimap.cpp +++ b/src/rendering/minimap.cpp @@ -10,6 +10,8 @@ #include "pipeline/blp_loader.hpp" #include "core/coordinates.hpp" #include "core/logger.hpp" +#include +#include #include #include #include @@ -234,6 +236,9 @@ void Minimap::shutdown() { if (noDataTexture) { noDataTexture->destroy(device, alloc); noDataTexture.reset(); } if (compositeTarget) { compositeTarget->destroy(device, alloc); compositeTarget.reset(); } + if (arrowDS_) { ImGui_ImplVulkan_RemoveTexture(arrowDS_); arrowDS_ = VK_NULL_HANDLE; } + if (arrowTexture_) { arrowTexture_->destroy(device, alloc); arrowTexture_.reset(); } + vkCtx = nullptr; } @@ -543,6 +548,7 @@ void Minimap::render(VkCommandBuffer cmd, const Camera& playerCamera, push.rect = glm::vec4(x, y, pixelW, pixelH); push.playerUV = glm::vec2(playerU, playerV); push.rotation = rotation; + arrowRotation_ = arrowRotation; push.arrowRotation = arrowRotation; push.zoomRadius = zoomRadius; push.squareShape = squareShape ? 1 : 0; diff --git a/src/rendering/world_map/coordinate_projection.cpp b/src/rendering/world_map/coordinate_projection.cpp index 9ca1b8b8..f74b0ee9 100644 --- a/src/rendering/world_map/coordinate_projection.cpp +++ b/src/rendering/world_map/coordinate_projection.cpp @@ -49,11 +49,7 @@ glm::vec2 renderPosToMapUV(const glm::vec3& renderPos, float u = (bounds.locLeft - wowX) / denom_h; float v = (bounds.locTop - wowY) / denom_v; - if (isContinent) { - constexpr float kVScale = 1.0f; - constexpr float kVOffset = -0.15f; - v = (v - 0.5f) * kVScale + 0.5f + kVOffset; - } + (void)isContinent; return glm::vec2(u, v); } diff --git a/src/rendering/world_map/layers/coordinate_display.cpp b/src/rendering/world_map/layers/coordinate_display.cpp index bd16cb56..d25bc564 100644 --- a/src/rendering/world_map/layers/coordinate_display.cpp +++ b/src/rendering/world_map/layers/coordinate_display.cpp @@ -31,9 +31,6 @@ void CoordinateDisplay::render(const LayerContext& ctx) { float l, r, t, b; getContinentProjectionBounds(*ctx.zones, ctx.currentZoneIdx, l, r, t, b); left = l; right = r; top = t; bottom = b; - // Undo the kVOffset applied during renderPosToMapUV for continent - constexpr float kVOffset = -0.15f; - mv -= kVOffset; } float hWowX = left - mu * (left - right); diff --git a/src/rendering/world_map/layers/player_marker_layer.cpp b/src/rendering/world_map/layers/player_marker_layer.cpp index f808f770..64c8899e 100644 --- a/src/rendering/world_map/layers/player_marker_layer.cpp +++ b/src/rendering/world_map/layers/player_marker_layer.cpp @@ -1,14 +1,76 @@ // player_marker_layer.cpp — Directional player arrow on the world map. -// Extracted from WorldMap::renderImGuiOverlay (Phase 8 of refactoring plan). +// Uses the WoW worldmapplayericon.blp texture, rendered as a rotated quad. #include "rendering/world_map/layers/player_marker_layer.hpp" #include "rendering/world_map/coordinate_projection.hpp" +#include "rendering/vk_texture.hpp" +#include "rendering/vk_context.hpp" +#include "pipeline/asset_manager.hpp" +#include "core/logger.hpp" #include +#include #include +#include namespace wowee { namespace rendering { namespace world_map { +PlayerMarkerLayer::~PlayerMarkerLayer() { + if (vkCtx_) { + VkDevice device = vkCtx_->getDevice(); + VmaAllocator alloc = vkCtx_->getAllocator(); + if (imguiDS_) ImGui_ImplVulkan_RemoveTexture(imguiDS_); + if (texture_) texture_->destroy(device, alloc); + } +} + +void PlayerMarkerLayer::initialize(VkContext* ctx, pipeline::AssetManager* am) { + vkCtx_ = ctx; + assetManager_ = am; +} + +void PlayerMarkerLayer::clearTexture() { + if (vkCtx_) { + VkDevice device = vkCtx_->getDevice(); + VmaAllocator alloc = vkCtx_->getAllocator(); + if (imguiDS_) { ImGui_ImplVulkan_RemoveTexture(imguiDS_); imguiDS_ = VK_NULL_HANDLE; } + if (texture_) { texture_->destroy(device, alloc); texture_.reset(); } + } + loadAttempted_ = false; +} + +void PlayerMarkerLayer::ensureTexture() { + if (loadAttempted_ || !vkCtx_ || !assetManager_) return; + loadAttempted_ = true; + + VkDevice device = vkCtx_->getDevice(); + + auto blp = assetManager_->loadTexture("Interface\\Minimap\\MinimapArrow.blp"); + if (!blp.isValid()) { + LOG_WARNING("PlayerMarkerLayer: MinimapArrow.blp not found"); + return; + } + auto tex = std::make_unique(); + if (!tex->upload(*vkCtx_, blp.data.data(), blp.width, blp.height, + VK_FORMAT_R8G8B8A8_UNORM, false)) + return; + if (!tex->createSampler(device, VK_FILTER_LINEAR, VK_FILTER_LINEAR, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 1.0f)) { + tex->destroy(device, vkCtx_->getAllocator()); + return; + } + VkDescriptorSet ds = ImGui_ImplVulkan_AddTexture( + tex->getSampler(), tex->getImageView(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + if (!ds) { + tex->destroy(device, vkCtx_->getAllocator()); + return; + } + texture_ = std::move(tex); + imguiDS_ = ds; + LOG_INFO("PlayerMarkerLayer: loaded MinimapArrow.blp ", blp.width, "x", blp.height); +} + void PlayerMarkerLayer::render(const LayerContext& ctx) { if (ctx.currentZoneIdx < 0) return; if (ctx.viewLevel != ViewLevel::ZONE && ctx.viewLevel != ViewLevel::CONTINENT) return; @@ -18,8 +80,6 @@ void PlayerMarkerLayer::render(const LayerContext& ctx) { ZoneBounds bounds = zone.bounds; bool isContinent = zone.areaID == 0; - // In continent view, only show the player marker if they are actually - // in a zone belonging to this continent (don't bleed across continents). if (isContinent) { int playerZone = findZoneForPlayer(*ctx.zones, ctx.playerRenderPos); if (playerZone < 0 || !zoneBelongsToContinent(*ctx.zones, playerZone, ctx.currentZoneIdx)) @@ -37,19 +97,47 @@ void PlayerMarkerLayer::render(const LayerContext& ctx) { float px = ctx.imgMin.x + playerUV.x * ctx.displayW; float py = ctx.imgMin.y + playerUV.y * ctx.displayH; - // Directional arrow: render-space (cos,sin) maps to screen (-dx,-dy) + // WoW yaw: 0° = North (+X in WoW = +Y render), increases counter-clockwise. + // Screen: +X = right, +Y = down. North on map = up = -Y screen. + // The BLP arrow points up (north) at 0 rotation, so we rotate by -yaw. float yawRad = glm::radians(ctx.playerYawDeg); - float adx = -std::cos(yawRad); - float ady = -std::sin(yawRad); - float apx = -ady, apy = adx; - constexpr float TIP = 9.0f; - constexpr float TAIL = 4.0f; - constexpr float HALF = 5.0f; - ImVec2 tip(px + adx * TIP, py + ady * TIP); - ImVec2 bl (px - adx * TAIL + apx * HALF, py - ady * TAIL + apy * HALF); - ImVec2 br (px - adx * TAIL - apx * HALF, py - ady * TAIL - apy * HALF); - ctx.drawList->AddTriangleFilled(tip, bl, br, IM_COL32(255, 40, 40, 255)); - ctx.drawList->AddTriangle(tip, bl, br, IM_COL32(0, 0, 0, 200), 1.5f); + float cosA = std::cos(-yawRad); + float sinA = std::sin(-yawRad); + + ensureTexture(); + + if (imguiDS_) { + constexpr float ARROW_HALF = 16.0f; + + // 4 corners of the unrotated quad (TL, TR, BR, BL) + float cx[4] = { -ARROW_HALF, ARROW_HALF, ARROW_HALF, -ARROW_HALF }; + float cy[4] = { -ARROW_HALF, -ARROW_HALF, ARROW_HALF, ARROW_HALF }; + + ImVec2 p[4]; + for (int i = 0; i < 4; i++) { + p[i].x = px + cx[i] * cosA - cy[i] * sinA; + p[i].y = py + cx[i] * sinA + cy[i] * cosA; + } + + ctx.drawList->AddImageQuad( + reinterpret_cast(imguiDS_), + p[0], p[1], p[2], p[3], + ImVec2(0, 0), ImVec2(1, 0), ImVec2(1, 1), ImVec2(0, 1), + IM_COL32_WHITE); + } else { + // Fallback: red triangle if texture failed to load + float adx = -std::cos(yawRad); + float ady = -std::sin(yawRad); + float apx_ = -ady, apy_ = adx; + constexpr float TIP = 9.0f; + constexpr float TAIL = 4.0f; + constexpr float FHALF = 5.0f; + ImVec2 tip(px + adx * TIP, py + ady * TIP); + ImVec2 bl (px - adx * TAIL + apx_ * FHALF, py - ady * TAIL + apy_ * FHALF); + ImVec2 br (px - adx * TAIL - apx_ * FHALF, py - ady * TAIL - apy_ * FHALF); + ctx.drawList->AddTriangleFilled(tip, bl, br, IM_COL32(255, 40, 40, 255)); + ctx.drawList->AddTriangle(tip, bl, br, IM_COL32(0, 0, 0, 200), 1.5f); + } } } // namespace world_map diff --git a/src/rendering/world_map/layers/zone_highlight_layer.cpp b/src/rendering/world_map/layers/zone_highlight_layer.cpp index dec4a659..7b459496 100644 --- a/src/rendering/world_map/layers/zone_highlight_layer.cpp +++ b/src/rendering/world_map/layers/zone_highlight_layer.cpp @@ -163,8 +163,45 @@ void ZoneHighlightLayer::render(const LayerContext& ctx) { hoveredZone_ = -1; ImVec2 mousePos = ImGui::GetMousePos(); - // ── Render zone rectangles using DBC world-coord AABB projection ── - // (Restored from old WorldMap::renderImGuiOverlay — no ZMP dependency) + // ── ZMP pixel-accurate hover detection ── + // The ZMP is a 128x128 grid covering the full world (64×64 ADTs of 533.333 each). + // Convert mouse screen position → world coordinates → ZMP grid cell → areaID → zone. + int zmpHoveredZone = -1; + if (ctx.hasZmpData && ctx.zmpGrid && ctx.zmpResolveZoneIdx && ctx.zmpRepoPtr) { + float mu = (mousePos.x - ctx.imgMin.x) / ctx.displayW; + float mv = (mousePos.y - ctx.imgMin.y) / ctx.displayH; + + if (mu >= 0.0f && mu <= 1.0f && mv >= 0.0f && mv <= 1.0f) { + // Undo the -0.15 vertical offset applied during continent rendering + constexpr float kVOffset = -0.15f; + mv -= kVOffset; + + // Screen UV → world coordinates + float wowX = cLeft - mu * cDenomU; + float wowY = cTop - mv * cDenomV; + + // World coordinates → ZMP UV (0.5 = world center) + constexpr float kWorldSize = 64.0f * 533.333f; // 34133.312 + float zmpX = 0.5f - wowX / kWorldSize; + float zmpY = 0.5f - wowY / kWorldSize; + + if (zmpX >= 0.0f && zmpX < 1.0f && zmpY >= 0.0f && zmpY < 1.0f) { + int col = static_cast(zmpX * 128.0f); + int row = static_cast(zmpY * 128.0f); + col = std::clamp(col, 0, 127); + row = std::clamp(row, 0, 127); + uint32_t areaId = (*ctx.zmpGrid)[row * 128 + col]; + if (areaId != 0) { + int zi = ctx.zmpResolveZoneIdx(ctx.zmpRepoPtr, areaId); + if (zi >= 0 && zoneBelongsToContinent(*ctx.zones, zi, ctx.continentIdx)) { + zmpHoveredZone = zi; + } + } + } + } + } + + // ── Render zone rectangles ── for (int zi = 0; zi < static_cast(ctx.zones->size()); zi++) { if (!zoneBelongsToContinent(*ctx.zones, zi, ctx.continentIdx)) continue; const auto& z = (*ctx.zones)[zi]; @@ -184,26 +221,26 @@ void ZoneHighlightLayer::render(const LayerContext& ctx) { zuMin = cu - hu; zuMax = cu + hu; zvMin = cv - hv; zvMax = cv + hv; - constexpr float kVOffset = -0.15f; - zvMin = (zvMin - 0.5f) + 0.5f + kVOffset; - zvMax = (zvMax - 0.5f) + 0.5f + kVOffset; - zuMin = std::clamp(zuMin, 0.0f, 1.0f); zuMax = std::clamp(zuMax, 0.0f, 1.0f); zvMin = std::clamp(zvMin, 0.0f, 1.0f); zvMax = std::clamp(zvMax, 0.0f, 1.0f); if (zuMax - zuMin < 0.001f || zvMax - zvMin < 0.001f) continue; + float titleBarH = ImGui::GetFrameHeight(); float sx0 = ctx.imgMin.x + zuMin * ctx.displayW; - float sy0 = ctx.imgMin.y + zvMin * ctx.displayH; + float sy0 = ctx.imgMin.y + zvMin * ctx.displayH + titleBarH; float sx1 = ctx.imgMin.x + zuMax * ctx.displayW; - float sy1 = ctx.imgMin.y + zvMax * ctx.displayH; + float sy1 = ctx.imgMin.y + zvMax * ctx.displayH + titleBarH; bool explored = !ctx.exploredZones || ctx.exploredZones->empty() || ctx.exploredZones->count(zi) > 0; - bool hovered = (mousePos.x >= sx0 && mousePos.x <= sx1 && - mousePos.y >= sy0 && mousePos.y <= sy1); + // Use ZMP pixel-accurate hover when available; fall back to AABB + bool hovered = (zmpHoveredZone >= 0) + ? (zi == zmpHoveredZone) + : (mousePos.x >= sx0 && mousePos.x <= sx1 && + mousePos.y >= sy0 && mousePos.y <= sy1); if (hovered) { hoveredZone_ = zi; diff --git a/src/rendering/world_map/world_map_facade.cpp b/src/rendering/world_map/world_map_facade.cpp index 2c593ce0..9213790c 100644 --- a/src/rendering/world_map/world_map_facade.cpp +++ b/src/rendering/world_map/world_map_facade.cpp @@ -122,6 +122,7 @@ struct WorldMapFacade::Impl { QuestPOILayer* questPOILayer = nullptr; CorpseMarkerLayer* corpseMarkerLayer = nullptr; ZoneHighlightLayer* zoneHighlightLayer = nullptr; + PlayerMarkerLayer* playerMarkerLayer = nullptr; // Data set each frame from the UI layer std::vector partyDots; @@ -242,7 +243,9 @@ void WorldMapFacade::Impl::initOverlayLayers() { overlay.addLayer(std::move(zhLayer)); // Player marker - overlay.addLayer(std::make_unique()); + auto pmLayer = std::make_unique(); + playerMarkerLayer = pmLayer.get(); + overlay.addLayer(std::move(pmLayer)); // Party dots auto pdLayer = std::make_unique(); @@ -293,6 +296,8 @@ bool WorldMapFacade::initialize(VkContext* ctx, pipeline::AssetManager* am) { if (!impl_->compositor.initialize(ctx, am)) return false; if (impl_->zoneHighlightLayer) impl_->zoneHighlightLayer->initialize(ctx, am); + if (impl_->playerMarkerLayer) + impl_->playerMarkerLayer->initialize(ctx, am); impl_->initialized = true; return true; } @@ -549,11 +554,9 @@ void WorldMapFacade::Impl::renderImGuiOverlay(const glm::vec3& playerRenderPos, float sw = static_cast(screenWidth); float sh = static_cast(screenHeight); - // Use the full FBO (1024×768) for aspect ratio — all coordinate math - // (kVOffset, zone DBC projection, ZMP grid) is calibrated for the full - // tile grid, not the cropped 1002×668 content area. - float mapAspect = static_cast(CompositeRenderer::FBO_W) / - static_cast(CompositeRenderer::FBO_H); + // Use the visible WoW map area (1002×668) for aspect ratio. + float mapAspect = static_cast(CompositeRenderer::MAP_W) / + static_cast(CompositeRenderer::MAP_H); float availW = sw * 0.70f; float availH = sh * 0.70f; float displayW, displayH; @@ -568,12 +571,17 @@ void WorldMapFacade::Impl::renderImGuiOverlay(const glm::vec3& playerRenderPos, // Floor to pixel boundary displayW = std::floor(displayW); displayH = std::floor(displayH); - float mapX = std::floor((sw - displayW) / 2.0f); - float mapY = std::floor((sh - displayH) / 2.0f); + + // Account for the ImGui title bar so the content area matches the map + float titleBarH = ImGui::GetFrameHeight(); + float windowW = displayW; + float windowH = displayH + titleBarH; + float mapX = std::floor((sw - windowW) / 2.0f); + float mapY = std::floor((sh - windowH) / 2.0f); // Map window — styled like the character selection window ImGui::SetNextWindowPos(ImVec2(mapX, mapY), ImGuiCond_Once); - ImGui::SetNextWindowSize(ImVec2(displayW, displayH), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(windowW, windowH), ImGuiCond_Always); ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | @@ -597,12 +605,12 @@ void WorldMapFacade::Impl::renderImGuiOverlay(const glm::vec3& playerRenderPos, ImVec2 imgMax(contentPos.x + contentSize.x, contentPos.y + contentSize.y); displayW = contentSize.x; displayH = contentSize.y; - // Show the full 1024×768 FBO — coordinate math (kVOffset, ZMP grid, - // DBC zone projection) is all calibrated for the full tile grid. + // Show only the visible 1002×668 content region of the 1024×768 FBO. ImGui::Image( reinterpret_cast(compositor.displayDescriptorSet()), ImVec2(displayW, displayH), - ImVec2(0, 0), ImVec2(1, 1)); + ImVec2(0, 0), ImVec2(CompositeRenderer::MAP_U_MAX, + CompositeRenderer::MAP_V_MAX)); // Transition fade overlay const auto& trans = viewState.transition(); @@ -971,15 +979,19 @@ void WorldMapFacade::Impl::renderImGuiOverlay(const glm::vec3& playerRenderPos, // • Full stretch (like WoW original): hlW = displayW, hlH = displayH // • Shift glow position: adjust hlX offset // - float hlW = displayW; // width of highlight rect (= square) - float hlH = displayH; // height of highlight rect (= square) - float hlX, hlY; + float hlW,hlH,hlX, hlY; if (cosmicLabel == "azeroth") { - hlX = imgMax.x - hlW; // flush right - hlY = imgMax.y - hlH; // flush bottom + hlW = displayW * 0.90f; // width of highlight rect (= square) + hlH = displayH * 0.985f; // height of highlight rect (= square) + + hlX = imgMax.x - hlW; // flush right + hlY = imgMax.y - hlH; // flush bottom + title bar } else { - hlX = imgMin.x; // flush left - hlY = imgMin.y; // flush top + hlW = displayW * 0.86f; // width of highlight rect (= square) + hlH = displayH * 0.91f; // height of highlight rect (= square) + + hlX = imgMin.x + displayW * 0.02f; // flush left + hlY = imgMax.y - displayH * 0.95f; // flush bottom } if (zoneHighlightLayer) { diff --git a/tests/test_world_map_coordinate_projection.cpp b/tests/test_world_map_coordinate_projection.cpp index 1b243280..ebff8b86 100644 --- a/tests/test_world_map_coordinate_projection.cpp +++ b/tests/test_world_map_coordinate_projection.cpp @@ -79,9 +79,9 @@ TEST_CASE("renderPosToMapUV: continent applies vertical offset", "[world_map][co glm::vec2 zone_uv = renderPosToMapUV(center, bounds, false); glm::vec2 cont_uv = renderPosToMapUV(center, bounds, true); - // Continent mode applies kVOffset = -0.15 + // No vertical offset — continent and zone UV should be identical REQUIRE(zone_uv.x == Catch::Approx(cont_uv.x).margin(0.01f)); - REQUIRE(cont_uv.y != Catch::Approx(zone_uv.y).margin(0.01f)); + REQUIRE(cont_uv.y == Catch::Approx(zone_uv.y).margin(0.01f)); } // ── zoneBelongsToContinent ───────────────────────────────────