Skip to content

feat: add satellite map functionality in Cyclopedia#1662

Open
zoelner wants to merge 8 commits intoopentibiabr:mainfrom
zoelner:feat/add-surface-view
Open

feat: add satellite map functionality in Cyclopedia#1662
zoelner wants to merge 8 commits intoopentibiabr:mainfrom
zoelner:feat/add-surface-view

Conversation

@zoelner
Copy link

@zoelner zoelner commented Mar 11, 2026

  • feat: add satellite map functionality in Cyclopedia
  • Added SatelliteMap class to manage satellite view image chunks.
  • Integrated satellite map loading and rendering in the Cyclopedia map.
  • Enhanced UI to support Surface and Map view toggling based on player position.
  • Updated map UI layout to accommodate new view options.
  • Improved minimap functionality to handle satellite mode.
  • Added Lua bindings for satellite map functions.
  • Refactored existing map loading logic to ensure compatibility with new features.
  • feat: Enhance minimap functionality with static minimap support and satellite chunk handling
  • feat: preserve initial zoom for Cyclopedia minimap and handle alternatives safely
  • feat: optimize LOD selection and repaint on satellite mode changes in minimap

Preview:

minimap-surface-view.mp4

Summary by CodeRabbit

  • New Features

    • Satellite map subsystem with on‑demand, floor‑aware tile loading (multi‑LOD), static/minimap rendering, and floor‑separator opacity control; new UI toggles for Surface/Map and satellite/static modes.
  • Bug Fixes

    • Preserved minimap zoom/state across loads; fixed minimap click/zoom handlers and improved widget visibility based on zoom.
  • Refactor

    • Minimap/Cyclopedia UI reorganized with a new Views grouping, layout adjustments, and floor-aware view initialization.

* feat: Implement satellite map functionality in Cyclopedia

- Added SatelliteMap class to manage satellite view image chunks.
- Integrated satellite map loading and rendering in the Cyclopedia map.
- Enhanced UI to support Surface and Map view toggling based on player position.
- Updated map UI layout to accommodate new view options.
- Improved minimap functionality to handle satellite mode.
- Added Lua bindings for satellite map functions.
- Refactored existing map loading logic to ensure compatibility with new features.

* feat: Enhance minimap functionality with static minimap support and satellite chunk handling

* feat: Remove obsolete satellite chunk formula documentation

* feat: preserve initial zoom for Cyclopedia minimap and handle alternatives safely

* feat: optimize LOD selection and repaint on satellite mode changes in minimap

* feat: add CLAUDE.md for project context and guidelines reference
@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

Adds a SatelliteMap subsystem (C++) with on-disk chunk indexing and lazy texture loading, exposes it to Lua, and integrates floor-aware satellite/static-minimap rendering plus new view UI and floor controls in the Cyclopedia map tab.

Changes

Cohort / File(s) Summary
Satellite Map Core
src/client/satellitemap.h, src/client/satellitemap.cpp
New SatelliteMap class: directory scanning, per-floor/minimap file cache, LZMA decompression, BMP parsing, lazy texture loading, LOD selection, and draw paths. Public APIs for load/draw/query added.
Minimap UI Integration (C++)
src/client/uiminimap.h, src/client/uiminimap.cpp, src/client/luafunctions.cpp
UIMinimap flags and opacity controls added; conditional draw paths to use SatelliteMap; Lua bindings expose g_satelliteMap and new minimap controls.
Cyclopedia Map Tab (Lua + OTUI)
modules/game_cyclopedia/tab/map/map.lua, modules/game_cyclopedia/tab/map/map.otui
Floor-aware minimap loading and Surface/Map view toggling, virtualFloor priming, ensureViewFloorsLoaded/refreshVirtualFloors, preserved minimap zoom, viewRadioGroup UI wiring, layout adjustments, floor navigation hooks.
Minimap Lua Helper
modules/gamelib/ui/uiminimap.lua
Guarded iteration over alternatives in zoom-change handling to avoid nil access and preserve visibility logic.
Map Rendering Color Change
src/client/uimap.cpp
Map preDraw color now depends on camera Z vs configured sea floor (dynamic color selection).
Build & Project
src/CMakeLists.txt, vc18/otclient.vcxproj, src/framework/graphics/ttfloader.cpp
Added satellitemap.cpp/satellitemap.h to build/project; added texture.h include in ttfloader.
Other Cyclopedia internals
modules/game_cyclopedia/tab/map/...
New internal helpers and stateful UI controls for floor-aware minimap behavior; public Cyclopedia API signatures unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant CycloLua as Cyclopedia (Lua)
    participant SatMap as SatelliteMap (C++)
    participant Tex as Texture Loader
    participant Renderer as Renderer
    participant Engine as Game Engine

    CycloLua->>SatMap: loadDirectory(dir) / loadFloors(dir,min,max)
    SatMap->>SatMap: scan directory, build per-floor indices

    Engine->>CycloLua: onUpdateCameraPosition()
    CycloLua->>CycloLua: detect floor/view change
    CycloLua->>SatMap: ensureViewFloorsLoaded(view/floor)
    SatMap->>Tex: lazily load required chunk textures

    CycloLua->>SatMap: draw(screenRect,cameraPos,scale,color,sepOpacity)
    SatMap->>SatMap: pick LODs, compute visible tiles
    SatMap->>Tex: loadChunkTexture(path)
    Tex->>Tex: decompressLzma() & parseBmp()
    Tex-->>SatMap: texture ready / error
    SatMap->>Renderer: composite draw calls
    Renderer-->>Engine: render frame
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • libergod
  • majestyotbr

Poem

🐰 I hopped through tiles both near and far,
Floors stacked like books beneath the star,
Chunks arrive as I softly peer,
Views flip quick, the map draws clear,
A rabbit's trail stitched pixel by pixel.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add satellite map functionality in Cyclopedia' accurately and specifically describes the main change: introducing satellite map functionality integrated into the Cyclopedia map system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@modules/game_cyclopedia/tab/map/map.lua`:
- Around line 87-92: In loadMap(), after priming virtualFloor from the player
position (the block that sets virtualFloor = pos.z), call refreshVirtualFloors()
to initialize layersMark and automapLayers immediately so the UI reflects the
correct floor; also apply the same fix to the other initialization site that
sets virtualFloor (the second occurrence) so both code paths call
refreshVirtualFloors() after assigning virtualFloor.
- Around line 169-174: The filter lookup is ambiguous because two CheckBoxButton
elements in map.otui share the same id "12", so change the UI ids to be unique
(e.g., prefix with "filter_" or "flag_") and update the code that finds the
checkbox (the markList:getChildById(tostring(flag.icon)) call in map.lua) to use
the new composite id (e.g., markList:getChildById("filter_" .. flag.icon)) or
alternatively change the lookup to iterate markList:getChildren() and match a
custom attribute on the CheckBoxButton to flag.icon; update either the map.otui
CheckBoxButton ids or the map.lua lookup consistently so flags always resolve to
the correct checkbox.

In `@src/client/satellitemap.cpp`:
- Around line 48-52: When loadFloors(const std::string& dir, ...) is called with
a different directory you must clear the previously indexed chunk state so old
ChunkKey entries don't reference stale files: detect a directory change (either
by adding a stored lastDir member or by making buildFileCache return a boolean
indicating it rebuilt for a new dir) and when a change is detected, clear
m_chunks and m_mmChunks and reset any related indexes or caches before
proceeding to repopulate from the new directory; update loadFloors to perform
this clear using the identified symbols (loadFloors, buildFileCache, m_chunks,
m_mmChunks, ChunkKey).
- Around line 125-135: The draw routine is currently hardcoding an opaque black
background and unmodulated texture draws, which ignores the caller-supplied
color; update SatelliteMap::draw to pass the provided color into the background
fill and to modulate all texture draw calls with that color (preserving alpha).
Concretely, replace the g_drawPool.addFilledRect(screenRect, Color::black) call
with g_drawPool.addFilledRect(screenRect, color) and change any
g_drawPool.addTexturedRect / addTexturedRectPart (and similar texture-queueing
calls used in the other render paths noted around lines 185, 193-201, 243) to
include the color parameter so the minimap tint/alpha from the widget is
applied. Ensure you use the same const Color& color parameter from
SatelliteMap::draw in each place.
- Around line 144-148: The loop currently iterates over {coarserLod, bestLod}
which yields duplicate LODs when bestLod == coarserLod (e.g., bestLod==64),
causing double drawing; update the LOD iteration in the nested loop that uses
bestLod and coarserLod (the for (int floor = SURFACE_FLOOR; floor >=
targetFloor; --floor) { for (const int lod : { coarserLod, bestLod }) { ... } }
pattern) so it only processes each distinct LOD once—either branch to a
single-pass when coarserLod == bestLod or conditionally iterate both LODs only
when they differ; apply the same fix to the other occurrence that mirrors this
pattern (the second loop mentioned around the 207-209 region).
- Around line 177-182: The code treats ChunkInfo::texture==nullptr as "not
loaded yet", so failed loads are retried every repaint; modify the chunk load
logic to cache failures by adding a failure marker on ChunkInfo (e.g., bool
loadFailed or an enum/state) and only attempt loadChunkTexture(info.path) if
loadFailed==false and texture==nullptr; when loadChunkTexture returns null, set
info.loadFailed=true and record/log the error once (instead of leaving texture
null), and update the same logic for both places that call loadChunkTexture (the
block using m_chunks.at(key) around ChunkInfo::texture and the other occurrence
at lines ~236-240) so subsequent repaints skip retries.
- Around line 354-383: The decompression loop can append arbitrarily large
decompressed data from a malformed .bmp.lzma; before inserting buf[0..got) into
output enforce a hard cap on chunk size and/or total output size: after calling
lzma_code(&strm, action) compute got = buf.size() - strm.avail_out then clamp
got to a safe MAX_CHUNK (and check output.size() + got against a MAX_TOTAL_SIZE)
and only insert the clamped number of bytes into output; use the existing
symbols strm, buf, output, lzma_code, LZMA_STREAM_END and ensure parseBmp() will
later still validate overall image size.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc0558db-3b36-43b9-a386-7b2d31a3483c

📥 Commits

Reviewing files that changed from the base of the PR and between d1bc526 and afd52f2.

📒 Files selected for processing (10)
  • modules/game_cyclopedia/tab/map/map.lua
  • modules/game_cyclopedia/tab/map/map.otui
  • modules/gamelib/ui/uiminimap.lua
  • src/CMakeLists.txt
  • src/client/luafunctions.cpp
  • src/client/satellitemap.cpp
  • src/client/satellitemap.h
  • src/client/uiminimap.cpp
  • src/client/uiminimap.h
  • src/framework/graphics/ttfloader.cpp

Copy link
Contributor

@kokekanon kokekanon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/opentibiabr/otclient/blob/main/vc18/otclient.vcxproj
you forgot to add

+ <ClCompile Include="..\src\client\satellitemap.cpp" />
+  <ClInclude Include="..\src\client\satellitemap.h" />

@zoelner
Copy link
Author

zoelner commented Mar 12, 2026

https://github.com/opentibiabr/otclient/blob/main/vc18/otclient.vcxproj you forgot to add

+ <ClCompile Include="..\src\client\satellitemap.cpp" />
+  <ClInclude Include="..\src\client\satellitemap.h" />

Thanks bro, I didn't even realize about the Visual Studio project, I'll add it, I'm adjusting some details and I'll upload it together.

* feat: add floor separator opacity control to satellite map view

* feat: add satellite map source files to project configuration

* feat: update floor separator color and improve logging verbosity in satellite map

* feat: remove unused checkbox and fix zoom method name in map UI
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/game_cyclopedia/tab/map/map.otui (1)

415-426: ⚠️ Potential issue | 🔴 Critical

These zoom buttons now call a missing Lua function.

The handlers were updated to modules.game_cyclopedia.Cyclopedia.setZoom(...), but modules/game_cyclopedia/tab/map/map.lua still exports Cyclopedia.setZooom. Clicking either button will fail until the Lua function is renamed or these handlers keep the old name.

Suggested follow-up in modules/game_cyclopedia/tab/map/map.lua
function Cyclopedia.setZoom(zoom)
    if zoom then
        UI.MapBase.minimap:zoomIn()
    else
        UI.MapBase.minimap:zoomOut()
    end
end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_cyclopedia/tab/map/map.otui` around lines 415 - 426, The OTUI
handlers call modules.game_cyclopedia.Cyclopedia.setZoom but the Lua export is
misspelled as Cyclopedia.setZooom; rename the Lua function in
modules/game_cyclopedia/tab/map/map.lua from setZooom to setZoom (exact
identifier Cyclopedia.setZoom) and ensure its signature accepts a boolean
parameter (zoom) and calls UI.MapBase.minimap:zoomIn() when true and zoomOut()
when false so the onClick handlers in the map.otui work without errors.
♻️ Duplicate comments (5)
src/client/satellitemap.cpp (5)

126-135: ⚠️ Potential issue | 🟠 Major

Honor the caller-supplied color in both render paths.

Both APIs accept const Color& color, but the background and chunk draws are still hardcoded/unmodulated. That drops widget tint and alpha in satellite/static-minimap mode.

Also applies to: 200-200, 210-218, 260-260

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 126 - 135, The draw path in
SatelliteMap::draw is ignoring the caller-supplied color and uses hardcoded
colors for the background and chunk rendering; update the calls that render the
background (g_drawPool.addFilledRect) and the chunk drawing paths (the routines
that draw m_chunks / tile layers within SatelliteMap::draw) to modulate or
replace the hardcoded Color::black and Color(0xFFA54C27U) with the passed-in
const Color& color (respecting its alpha) and apply floorSeparatorOpacity where
appropriate so the widget tint and alpha are honored in both sea-floor and
non-sea-floor branches as well as the chunk draw loops referenced in this
function.

192-197: ⚠️ Potential issue | 🟡 Minor

Cache failed chunk loads instead of retrying every repaint.

texture == nullptr currently means both “not loaded yet” and “load failed”. Broken visible chunks will be re-read, re-decompressed, and re-logged every frame.

Also applies to: 253-257

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 192 - 197, Chunk loads currently
use info.texture == nullptr for both "not attempted" and "failed", causing
repeated expensive retries; add an explicit load state on ChunkInfo (e.g., bool
loadFailed or an enum LoadState) and use it in the visible-chunk loading logic
(the block that calls loadChunkTexture and the similar block around the lines
253-257) so that after a failed load you mark loadFailed=true (or state=Failed)
and skip reattempts on subsequent repaints; modify logic that checks
info.texture to also check the new load state and only call loadChunkTexture
when the state is "not attempted" (or Unknown) and set state to Loaded/Failed
based on the result from loadChunkTexture.

371-387: ⚠️ Potential issue | 🟠 Major

Cap decompressed output size before growing output.

This loop appends until LZMA_STREAM_END with no hard ceiling. A malformed .bmp.lzma can force arbitrarily large allocations before parseBmp() rejects it.

Suggested fix
     std::vector<uint8_t> output;
     output.reserve(512 * 512 * 4 + 2048); // typical 32-bpp BMP for 512×512
+    constexpr size_t MAX_DECOMPRESSED_SIZE = 512u * 512u * 4u + 4096u;
@@
         ret = lzma_code(&strm, action);

         const size_t got = buf.size() - strm.avail_out;
+        if (output.size() + got > MAX_DECOMPRESSED_SIZE) {
+            lzma_end(&strm);
+            g_logger.warning("SatelliteMap: decoded chunk exceeds {} bytes", MAX_DECOMPRESSED_SIZE);
+            return {};
+        }
         output.insert(output.end(), buf.begin(), buf.begin() + got);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 371 - 387, The loop that calls
lzma_code and appends into output (using buf, got, strm, ret) can grow unbounded
for malformed .bmp.lzma files; add a hard ceiling (e.g., MAX_DECOMPRESSED_SIZE)
and before inserting check if output.size() + got would exceed that ceiling,
then either truncate the bytes appended to stay within the limit and
break/return an error, or fail early; ensure the ceiling is enforced before
calling parseBmp so parseBmp is never handed a decompressed buffer larger than
the allowed MAX_DECOMPRESSED_SIZE.

145-147: ⚠️ Potential issue | 🟠 Major

Skip the extra pass when bestLod == 64.

At the coarsest zoom, { coarserLod, bestLod } becomes { 64, 64 }, so every visible chunk is loaded and drawn twice.

Suggested fix
-    const int coarserLod = (bestLod == 16) ? 32 : 64;
+    const int coarserLod = (bestLod == 16) ? 32 : (bestLod == 32 ? 64 : -1);

     for (const int lod : { coarserLod, bestLod }) {
+        if (lod < 0)
+            continue;
         const int indexKey = floor * 100 + lod;

Also applies to: 163-163, 224-226

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 145 - 147, The code computes
coarserLod from bestLod and ends up with coarserLod == bestLod at the coarsest
zoom, causing a duplicate load/draw pass; update the logic around pickLod and
coarserLod (variables bestLod and coarserLod) to avoid that: either set
coarserLod to a sentinel (e.g. 0 or -1) when it would equal bestLod, or add a
guard before the second pass that skips loading/drawing when coarserLod ==
bestLod; apply the same fix in the other occurrences referenced (the spots
around the other occurrences of bestLod/coarserLod).

49-52: ⚠️ Potential issue | 🟠 Major

Clear indexed chunks when loadFloors() switches directories.

buildFileCache() only resets the scan list. If loadFloors() is called with a different dir, the old m_chunks / m_mmChunks entries survive, and overlapping ChunkKeys keep pointing at stale files because the duplicate checks skip the new ones.

Suggested fix
 int SatelliteMap::loadFloors(const std::string& dir, const int floorMin, const int floorMax)
 {
+    if (!m_fileCacheDir.empty() && dir != m_fileCacheDir)
+        clear();
+
     // Build the file list exactly once per directory — reused on every subsequent call.
     buildFileCache(dir);

Also applies to: 79-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 49 - 52, loadFloors currently calls
buildFileCache(dir) but doesn't clear prior state, so when dir changes old
entries in m_chunks and m_mmChunks with overlapping ChunkKey point to stale
files; modify SatelliteMap::loadFloors to detect when the requested dir differs
from the cached directory (or always when switching directories) and clear/reset
m_chunks and m_mmChunks before calling buildFileCache (and before the duplicate
checks later in the function, e.g., the block around where duplicate-check logic
runs) so new files are not skipped by stale entries.
🧹 Nitpick comments (2)
src/client/uiminimap.h (1)

70-77: Make this header self-contained before using std::clamp.

setFloorSeparatorOpacity() is inline and calls std::clamp, but uiminimap.h does not include <algorithm>. Right now this compiles only if a transitive include or the PCH happens to provide it.

Suggested fix
 `#include` "declarations.h"
+#include <algorithm>
 `#include` <framework/ui/uiwidget.h>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/uiminimap.h` around lines 70 - 77, The header uiminimap.h uses
std::clamp inside the inline method setFloorSeparatorOpacity(float) but doesn't
include <algorithm>, making the header not self-contained; add a direct include
for <algorithm> at the top of uiminimap.h so std::clamp is always available,
then keep the implementation of setFloorSeparatorOpacity (which clamps, compares
to m_floorSeparatorOpacity and calls repaint()) unchanged.
modules/game_cyclopedia/tab/map/map.lua (1)

10-12: Don't duplicate the sea-floor threshold in Lua.

This bakes in the default 7, while the engine-side map logic already reads the boundary from g_gameConfig.getMapSeaFloor(). If a client overrides that config, Cyclopedia will enable the wrong view mode and floor-separator behavior. Please expose getMapSeaFloor() to Lua and derive SEA_FLOOR from config instead of hardcoding it here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_cyclopedia/tab/map/map.lua` around lines 10 - 12, Replace the
hardcoded SEA_FLOOR constant with a runtime value obtained from the engine
config: call g_gameConfig.getMapSeaFloor() instead of using the literal 7 (i.e.
remove/replace the local SEA_FLOOR = 7 and set SEA_FLOOR =
g_gameConfig.getMapSeaFloor()). If that engine getter is not yet bound to Lua,
add a Lua binding for getMapSeaFloor on the g_gameConfig interface so Lua can
call it; keep all existing uses of SEA_FLOOR (or its new variable) unchanged so
view-mode and floor-separator behavior derives from the engine config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/satellitemap.cpp`:
- Around line 435-443: The code currently calls std::abs on the int32_t variable
height and negates it, which can overflow for INT32_MIN; change the logic in the
block that reads height (uses readI32 and variable height, and determines
topDown) to compute the magnitude in a wider type first (e.g. int64_t mag =
(height < 0 ? -(int64_t)height : (int64_t)height)); use mag for the bounds check
(ensure mag > 0 and mag <= 4096) and for validating image size, retain topDown =
(height < 0), and only assign back to height after confirming mag fits in
int32_t (or reject if mag > INT32_MAX) to avoid undefined behavior from std::abs
or negation on INT32_MIN.

---

Outside diff comments:
In `@modules/game_cyclopedia/tab/map/map.otui`:
- Around line 415-426: The OTUI handlers call
modules.game_cyclopedia.Cyclopedia.setZoom but the Lua export is misspelled as
Cyclopedia.setZooom; rename the Lua function in
modules/game_cyclopedia/tab/map/map.lua from setZooom to setZoom (exact
identifier Cyclopedia.setZoom) and ensure its signature accepts a boolean
parameter (zoom) and calls UI.MapBase.minimap:zoomIn() when true and zoomOut()
when false so the onClick handlers in the map.otui work without errors.

---

Duplicate comments:
In `@src/client/satellitemap.cpp`:
- Around line 126-135: The draw path in SatelliteMap::draw is ignoring the
caller-supplied color and uses hardcoded colors for the background and chunk
rendering; update the calls that render the background
(g_drawPool.addFilledRect) and the chunk drawing paths (the routines that draw
m_chunks / tile layers within SatelliteMap::draw) to modulate or replace the
hardcoded Color::black and Color(0xFFA54C27U) with the passed-in const Color&
color (respecting its alpha) and apply floorSeparatorOpacity where appropriate
so the widget tint and alpha are honored in both sea-floor and non-sea-floor
branches as well as the chunk draw loops referenced in this function.
- Around line 192-197: Chunk loads currently use info.texture == nullptr for
both "not attempted" and "failed", causing repeated expensive retries; add an
explicit load state on ChunkInfo (e.g., bool loadFailed or an enum LoadState)
and use it in the visible-chunk loading logic (the block that calls
loadChunkTexture and the similar block around the lines 253-257) so that after a
failed load you mark loadFailed=true (or state=Failed) and skip reattempts on
subsequent repaints; modify logic that checks info.texture to also check the new
load state and only call loadChunkTexture when the state is "not attempted" (or
Unknown) and set state to Loaded/Failed based on the result from
loadChunkTexture.
- Around line 371-387: The loop that calls lzma_code and appends into output
(using buf, got, strm, ret) can grow unbounded for malformed .bmp.lzma files;
add a hard ceiling (e.g., MAX_DECOMPRESSED_SIZE) and before inserting check if
output.size() + got would exceed that ceiling, then either truncate the bytes
appended to stay within the limit and break/return an error, or fail early;
ensure the ceiling is enforced before calling parseBmp so parseBmp is never
handed a decompressed buffer larger than the allowed MAX_DECOMPRESSED_SIZE.
- Around line 145-147: The code computes coarserLod from bestLod and ends up
with coarserLod == bestLod at the coarsest zoom, causing a duplicate load/draw
pass; update the logic around pickLod and coarserLod (variables bestLod and
coarserLod) to avoid that: either set coarserLod to a sentinel (e.g. 0 or -1)
when it would equal bestLod, or add a guard before the second pass that skips
loading/drawing when coarserLod == bestLod; apply the same fix in the other
occurrences referenced (the spots around the other occurrences of
bestLod/coarserLod).
- Around line 49-52: loadFloors currently calls buildFileCache(dir) but doesn't
clear prior state, so when dir changes old entries in m_chunks and m_mmChunks
with overlapping ChunkKey point to stale files; modify SatelliteMap::loadFloors
to detect when the requested dir differs from the cached directory (or always
when switching directories) and clear/reset m_chunks and m_mmChunks before
calling buildFileCache (and before the duplicate checks later in the function,
e.g., the block around where duplicate-check logic runs) so new files are not
skipped by stale entries.

---

Nitpick comments:
In `@modules/game_cyclopedia/tab/map/map.lua`:
- Around line 10-12: Replace the hardcoded SEA_FLOOR constant with a runtime
value obtained from the engine config: call g_gameConfig.getMapSeaFloor()
instead of using the literal 7 (i.e. remove/replace the local SEA_FLOOR = 7 and
set SEA_FLOOR = g_gameConfig.getMapSeaFloor()). If that engine getter is not yet
bound to Lua, add a Lua binding for getMapSeaFloor on the g_gameConfig interface
so Lua can call it; keep all existing uses of SEA_FLOOR (or its new variable)
unchanged so view-mode and floor-separator behavior derives from the engine
config.

In `@src/client/uiminimap.h`:
- Around line 70-77: The header uiminimap.h uses std::clamp inside the inline
method setFloorSeparatorOpacity(float) but doesn't include <algorithm>, making
the header not self-contained; add a direct include for <algorithm> at the top
of uiminimap.h so std::clamp is always available, then keep the implementation
of setFloorSeparatorOpacity (which clamps, compares to m_floorSeparatorOpacity
and calls repaint()) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3fb46e31-ba0d-4519-a402-0d0167fd6b4a

📥 Commits

Reviewing files that changed from the base of the PR and between afd52f2 and c2fa350.

📒 Files selected for processing (9)
  • modules/game_cyclopedia/tab/map/map.lua
  • modules/game_cyclopedia/tab/map/map.otui
  • src/client/luafunctions.cpp
  • src/client/satellitemap.cpp
  • src/client/satellitemap.h
  • src/client/uimap.cpp
  • src/client/uiminimap.cpp
  • src/client/uiminimap.h
  • vc18/otclient.vcxproj

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/client/satellitemap.cpp (1)

142-143: ⚠️ Potential issue | 🟠 Major

Honor the widget color in both satellite render paths.

UIMinimap::drawSelf() passes m_color into these methods, but both paths still draw an opaque background and unmodulated textures here, so tint/alpha disappears when the widget switches from g_minimap.draw(...) to satellite/static-minimap rendering.

Also applies to: 216-216, 234-235, 284-284

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 142 - 143, The background and
texture draws in the satellite/static-minimap paths ignore the widget tint/alpha
(m_color) passed from UIMinimap::drawSelf()/g_minimap.draw(), so update the
calls (e.g., g_drawPool.addFilledRect and the texture draw calls) to modulate
the existing colors (sea floor Color(0xFFA54C27U) or Color::black) and any
opaque texture colors by the passed-in m_color (or the function parameter that
receives it) so the final color respects tint and alpha; find usages in the
satellite rendering paths referenced around g_drawPool.addFilledRect and ensure
the same multiplication/modulation is applied for all equivalent spots (also
where textures are drawn) so both satellite and static-minimap render paths
honor the widget color.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/satellitemap.cpp`:
- Around line 386-389: The liblzma decoder is being initialized with
lzma_alone_decoder(&strm, UINT64_MAX) which disables liblzma's memory cap;
change this to pass a bounded memlimit (e.g. size_t memlimit = 64ull * 1024 *
1024) instead of UINT64_MAX and store the lzma_alone_decoder return value in a
variable; then update the subsequent error handling (the check that currently
logs and returns) to explicitly detect LZMA_MEMLIMIT_ERROR and treat it as a
memory-limit failure (log a clear message and return) while still handling other
lzma_ret values as before; reference lzma_stream strm, lzma_alone_decoder,
MAX_DECOMPRESSED, and check for LZMA_MEMLIMIT_ERROR in the decompression error
branch.

---

Duplicate comments:
In `@src/client/satellitemap.cpp`:
- Around line 142-143: The background and texture draws in the
satellite/static-minimap paths ignore the widget tint/alpha (m_color) passed
from UIMinimap::drawSelf()/g_minimap.draw(), so update the calls (e.g.,
g_drawPool.addFilledRect and the texture draw calls) to modulate the existing
colors (sea floor Color(0xFFA54C27U) or Color::black) and any opaque texture
colors by the passed-in m_color (or the function parameter that receives it) so
the final color respects tint and alpha; find usages in the satellite rendering
paths referenced around g_drawPool.addFilledRect and ensure the same
multiplication/modulation is applied for all equivalent spots (also where
textures are drawn) so both satellite and static-minimap render paths honor the
widget color.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea9a6b14-6b54-4601-922f-ceed7aaf9bbf

📥 Commits

Reviewing files that changed from the base of the PR and between c2fa350 and f89f18d.

📒 Files selected for processing (2)
  • src/client/satellitemap.cpp
  • src/client/satellitemap.h

Copilot AI and others added 3 commits March 12, 2026 12:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/client/satellitemap.cpp (2)

386-394: ⚠️ Potential issue | 🟠 Major

Move the LZMA_MEMLIMIT_ERROR handling to the decode result path.

The decoder hasn't seen the stream header yet at Line 388, so the memory-limit failure won't be reported there for oversized-dictionary inputs. As written, those cases still fall through to the generic error at Line 437 instead of the clearer memlimit log/return branch.

In liblzma, can `lzma_alone_decoder()` itself return `LZMA_MEMLIMIT_ERROR`, or is `LZMA_MEMLIMIT_ERROR` reported by subsequent `lzma_code()` calls after the `.lzma` header has been read?

Also applies to: 437-439

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 386 - 394, The LZMA_MEMLIMIT_ERROR
check belongs in the decode/result handling, not the init path: remove or
simplify the LZMA_MEMLIMIT_ERROR branch after lzma_alone_decoder(&strm, ...)
(keep a generic init-failure log for non-OK initRet), and add handling where
lzma_code(...) (or the decode loop that consumes the .lzma header) returns
LZMA_MEMLIMIT_ERROR — log "SatelliteMap: LZMA decoder init failed: memory limit
exceeded" (or similar) and return {} there; ensure you reference the same
lzma_stream strm and use lzma_code's return value to detect LZMA_MEMLIMIT_ERROR
so oversized-dictionary inputs give the clearer memlimit path.

142-143: ⚠️ Potential issue | 🟠 Major

Honor the caller-supplied color in both render paths.

draw() / drawStaticMinimap() still hardcode the background fill and queue textures unmodulated, so widget tint/alpha disappears in satellite mode. Please thread color through the background and textured draws here as well.

Also applies to: 216-216, 234-235, 284-284

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 142 - 143, The background fill is
hardcoded to specific Colors and ignores the caller-supplied color/tint; update
the satellite rendering code paths (draw() / drawStaticMinimap()) to use the
incoming color/tint when queuing background and texture draws: replace the
hardcoded Color(0xFFA54C27U) / Color::black in the g_drawPool.addFilledRect call
with the caller-supplied color (respecting the sea-floor conditional using
cameraPos.z == g_gameConfig.getMapSeaFloor()), and ensure all textured queue
calls (the functions that enqueue sprite/texture draws in this file)
multiply/modulate their vertex colors by that same color/alpha so widget
tint/alpha is preserved in satellite mode (apply this change to the other
occurrences referenced around the g_drawPool.addFilledRect usage).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/satellitemap.cpp`:
- Line 216: The code currently assumes chunk bitmaps are 512x512 when calling
g_drawPool.addTexturedRect(dest, info.texture, Rect(0,0,512,512)) and may create
oversized textures; before creating or assigning info.texture (and before any
subsequent g_drawPool.addTexturedRect calls), validate the decoded image's width
and height are exactly 512 and fail fast (log error/return false/skip) if not,
releasing any decoded bitmap resources; apply the same exact size check and
early-fail logic wherever chunk textures are created/used (the code that
constructs info.texture and the callers that use Rect(0,0,512,512), e.g., the
other chunk-load sites referenced) so no non-512 BMP ever becomes a GPU texture.

---

Duplicate comments:
In `@src/client/satellitemap.cpp`:
- Around line 386-394: The LZMA_MEMLIMIT_ERROR check belongs in the
decode/result handling, not the init path: remove or simplify the
LZMA_MEMLIMIT_ERROR branch after lzma_alone_decoder(&strm, ...) (keep a generic
init-failure log for non-OK initRet), and add handling where lzma_code(...) (or
the decode loop that consumes the .lzma header) returns LZMA_MEMLIMIT_ERROR —
log "SatelliteMap: LZMA decoder init failed: memory limit exceeded" (or similar)
and return {} there; ensure you reference the same lzma_stream strm and use
lzma_code's return value to detect LZMA_MEMLIMIT_ERROR so oversized-dictionary
inputs give the clearer memlimit path.
- Around line 142-143: The background fill is hardcoded to specific Colors and
ignores the caller-supplied color/tint; update the satellite rendering code
paths (draw() / drawStaticMinimap()) to use the incoming color/tint when queuing
background and texture draws: replace the hardcoded Color(0xFFA54C27U) /
Color::black in the g_drawPool.addFilledRect call with the caller-supplied color
(respecting the sea-floor conditional using cameraPos.z ==
g_gameConfig.getMapSeaFloor()), and ensure all textured queue calls (the
functions that enqueue sprite/texture draws in this file) multiply/modulate
their vertex colors by that same color/alpha so widget tint/alpha is preserved
in satellite mode (apply this change to the other occurrences referenced around
the g_drawPool.addFilledRect usage).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09f229ab-2303-427e-a984-f8e5d3690568

📥 Commits

Reviewing files that changed from the base of the PR and between f89f18d and 4d2cd83.

📒 Files selected for processing (1)
  • src/client/satellitemap.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/client/satellitemap.cpp (1)

151-151: Consider extracting SURFACE_FLOOR as a class-level or file-level constant.

SURFACE_FLOOR = 7 is defined twice as a static constexpr local variable (lines 151 and 305). Extracting it to a single definition at file scope or as a class constant would reduce duplication.

💡 Suggested refactor
 static constexpr std::string_view PREFIX_SATELLITE = "satellite";
 static constexpr std::string_view PREFIX_MINIMAP   = "minimap";
+static constexpr int SURFACE_FLOOR = 7;

Then remove the local definitions inside draw() and hasChunksForView().

Also applies to: 305-305

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` at line 151, SURFACE_FLOOR is defined twice as a
local static constexpr in draw() and hasChunksForView(); extract a single
constant (e.g., constexpr int SURFACE_FLOOR = 7) at file scope or as a static
member of the surrounding class, replace the local definitions with references
to that single symbol, and remove the duplicated local declarations in draw()
and hasChunksForView(); ensure all uses still compile by updating any scope
qualifiers if you make it a class constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/client/satellitemap.cpp`:
- Line 151: SURFACE_FLOOR is defined twice as a local static constexpr in draw()
and hasChunksForView(); extract a single constant (e.g., constexpr int
SURFACE_FLOOR = 7) at file scope or as a static member of the surrounding class,
replace the local definitions with references to that single symbol, and remove
the duplicated local declarations in draw() and hasChunksForView(); ensure all
uses still compile by updating any scope qualifiers if you make it a class
constant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 399f54d7-8d80-4b43-a2ed-f8422b166cc0

📥 Commits

Reviewing files that changed from the base of the PR and between 4d2cd83 and 26716b0.

📒 Files selected for processing (1)
  • src/client/satellitemap.cpp

@zoelner
Copy link
Author

zoelner commented Mar 13, 2026

@coderabbitai resume

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

✅ Actions performed

Reviews resumed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/client/satellitemap.cpp (1)

135-145: ⚠️ Potential issue | 🟠 Major

color is still ignored in both render paths.

Line 135/226 takes const Color& color, but Line 144/234 uses hardcoded fill colors and Line 216/284 draw textures unmodulated. Satellite/static-minimap tint/alpha from callers won’t apply.

#!/bin/bash
# Verify draw API supports color modulation and confirm current call sites skip it.
# Expected: addTexturedRect/addTexturedRectPart overloads with Color exist,
# and SatelliteMap draw paths currently omit Color argument.

fd drawpoolmanager.h
rg -nP -C2 --type=cpp '\baddTexturedRect(Part)?\s*\('
rg -nP -C3 --type=cpp 'SatelliteMap::draw\s*\(|SatelliteMap::drawStaticMinimap\s*\(' src/client/satellitemap.cpp
rg -nP -C3 --type=cpp '\bg_satelliteMap\b|\bcolor\b' src/client/uiminimap.cpp src/client/uiminimap.h src/client/luafunctions.cpp

Also applies to: 216-216, 226-235, 284-284

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` around lines 135 - 145, The draw functions
(SatelliteMap::draw and SatelliteMap::drawStaticMinimap) currently ignore the
passed Color& color: replace hardcoded fill/texture draws so they use the
provided color (and floorSeparatorOpacity where relevant) when calling
g_drawPool.addFilledRect and the texture blit functions; specifically, update
the g_drawPool.addFilledRect call that uses Color(0xFFA54C27U)/Color::black to
modulate with color (e.g., multiply or blend with color and apply
floorSeparatorOpacity), and change
g_drawPool.addTexturedRect/addTexturedRectPart calls in SatelliteMap::draw (and
the analogous calls in drawStaticMinimap) to the overloads that accept a Color
so the passed-in color (with alpha applied) is used to tint both background and
textures; ensure you preserve existing conditional logic for sea floor vs others
while threading color/floorSeparatorOpacity through the draw calls.
🧹 Nitpick comments (1)
src/client/satellitemap.cpp (1)

429-429: Improve decompression-limit log context.

Line 429 logs a literal "chunk", which makes production triage harder. Consider threading the source path into decompressLzma(...) (or include a chunk key) so limit-hit logs identify the offending asset.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/satellitemap.cpp` at line 429, The warning uses a literal "chunk"
which hides which asset triggered the decompression-limit; modify
decompressLzma(...) to accept an identifying string (e.g. sourcePath or
chunkKey) and propagate that identifier from its call sites, then change the
g_logger.warning call in decompressLzma to include that identifier instead of
the literal ("chunk") so the log becomes e.g. "SatelliteMap: decompressed size
exceeds limit, aborting '{}'" with the passed identifier; update all callers of
decompressLzma and any related signatures accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/client/satellitemap.cpp`:
- Around line 135-145: The draw functions (SatelliteMap::draw and
SatelliteMap::drawStaticMinimap) currently ignore the passed Color& color:
replace hardcoded fill/texture draws so they use the provided color (and
floorSeparatorOpacity where relevant) when calling g_drawPool.addFilledRect and
the texture blit functions; specifically, update the g_drawPool.addFilledRect
call that uses Color(0xFFA54C27U)/Color::black to modulate with color (e.g.,
multiply or blend with color and apply floorSeparatorOpacity), and change
g_drawPool.addTexturedRect/addTexturedRectPart calls in SatelliteMap::draw (and
the analogous calls in drawStaticMinimap) to the overloads that accept a Color
so the passed-in color (with alpha applied) is used to tint both background and
textures; ensure you preserve existing conditional logic for sea floor vs others
while threading color/floorSeparatorOpacity through the draw calls.

---

Nitpick comments:
In `@src/client/satellitemap.cpp`:
- Line 429: The warning uses a literal "chunk" which hides which asset triggered
the decompression-limit; modify decompressLzma(...) to accept an identifying
string (e.g. sourcePath or chunkKey) and propagate that identifier from its call
sites, then change the g_logger.warning call in decompressLzma to include that
identifier instead of the literal ("chunk") so the log becomes e.g.
"SatelliteMap: decompressed size exceeds limit, aborting '{}'" with the passed
identifier; update all callers of decompressLzma and any related signatures
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 87506a9a-b327-45a9-9cd0-40dab5724a8d

📥 Commits

Reviewing files that changed from the base of the PR and between 4d2cd83 and 1b13443.

📒 Files selected for processing (1)
  • src/client/satellitemap.cpp

@majestyotbr
Copy link
Contributor

@kokekanon
Can you check and test this PR? Thanks.

@zoelner
Copy link
Author

zoelner commented Mar 20, 2026

@majestyotbr @kokekanon @dudantas Is there any impediment to merging this branch, or something blocking it that was not mentioned?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants