Skip to content

Minimap

Overview

The Minimap (src/client/ui/components/sidebar/Minimap.ts) displays a thumbnail of the map and allows clicking to jump the camera. It appears in two modes:

  1. Sidebar mode – inside the sidebar (classic layout)
  2. Overlay mode – top-right corner of the canvas (React layout, useReactLayout: true)

In overlay mode, the minimap has a header with: - Collapse/expand button (right side) – click to hide or show the map content (− / ▶) - Drag handle – drag the header to reposition the minimap anywhere on the canvas (uses window.addEventListener for move/up so drag works when pointer leaves the canvas)

Persistence

Position and collapsed state are saved to sessionStorage under simu-game-minimap-overlay: - Save: on drag end and on collapse/expand toggle - Restore: in loadMinimap() when overlay mode (restoreOverlayState()) - Stored: { x, y, collapsed } - sessionStorage = per-tab; clears when tab is closed

Click-to-jump implementation

Coordinate conversion (final approach)

Clicks are converted to tile coordinates using proportional position within the sprite's world bounds:

bounds = sprite.getBounds()                    // world-space AABB
relX = (globalX - bounds.x) / bounds.width     // 0..1
relY = (globalY - bounds.y) / bounds.height
tileX = relX * mapDimensions.width
tileY = relY * mapDimensions.height

Why not toLocal()? Pixi has multiple coordinate spaces (canvas pixels, resolution, stage). toLocal() and manual formulas mixed these, causing wrong teleports (e.g. center click → bottom-right). Using getBounds() + proportional 0–1 avoids coordinate system mismatches.

Click source: We use Pixi's event.global (or event.globalX/event.globalY) – it matches the space used by getBounds().

Map intercepting clicks (overlay mode)

In overlay mode, the main map could intercept minimap clicks. Fixes:

  1. Minimap sprite
  2. eventMode = 'static'
  3. hitArea = new Rectangle(0, 0, texture.width, texture.height) – full rect so transparent pixels also receive clicks

  4. Minimap container (overlay only)

  5. eventMode = 'static'
  6. hitArea = Rectangle(...) covering the whole overlay – blocks map from hit-testing underneath

  7. MapInputHandler

  8. New getExcludeOverlayBounds dep – returns minimap rect in canvas space
  9. On window pointerup, if pointer is inside that rect → skip map click handling
  10. GameScreen calls isometricMap.setExcludeOverlayBounds(() => minimapContainer.getBounds())

Debug

Enable via:

localStorage.setItem('DEBUG_MINIMAP_CLICKS', '1');
// refresh page

Disable:

localStorage.removeItem('DEBUG_MINIMAP_CLICKS');

Logs: client coords, global coords, sprite bounds, relative %, tile coords.

Files

File Role
Minimap.ts Minimap component, header (collapse/drag), click handling, viewport box, persistence
GameScreen.ts Creates overlay minimap when useReactLayout, passes clientToCanvasCoords, sets setExcludeOverlayBounds
MapInputHandler.ts getExcludeOverlayBounds – skips map processing when pointer over minimap
IsometricMap.ts setExcludeOverlayBounds(getter)
GameSidebar.tsx React sidebar – removed minimap spacer; speed controls moved up

Previous attempts (for reference)

  • clientToCanvasCoords – converts (clientX, clientY) to canvas internal pixels; used for window pointerup, not for minimap coordinate conversion
  • canvasToStageCoords – converting canvas → stage pixels; assumption about stage size was wrong, caused incorrect coords
  • toLocal() – depends on correct global/stage coords; mixing canvas pixels with stage caused ~2x error (center → bottom-right)