# Technical Architecture Report: Minimap HUD Component & DOM Mounting
**Agent**: `explorer_m4_3`  
**Milestone**: Milestone 4 / R4 (Fog of War & Minimap HUD)  
**Date**: 2026-10-01T21:24:35Z  
**Target Codebase**: `client/webapp/js/ui/minimap_hud.js`, `client/webapp/index.html`  

---

## 1. Executive Summary

This report establishes the technical architecture, mathematical projection model, visual design, and verification harness for the **Minimap HUD Component** (`client/webapp/js/ui/minimap_hud.js`) and its DOM integration into `client/webapp/index.html`.

### Key Specifications Delivered:
- **Dedicated Canvas**: $120 \times 80\text{px}$ mounted in top-right HUD container (`#minimap-container`).
- **Aspect-Ratio Preserving Projection**: Maps arbitrary procedural zone dimensions ($60 \times 45$ up to $120 \times 90$) with uniform scaling and automatic centering ($padX, padY$).
- **Dual-Layer Caching Architecture**: Base terrain and fog shroud are baked onto an offscreen $120 \times 80\text{px}$ canvas and only re-baked when dirty (`fogDirty || mapDirty`). Onscreen render loop only blits cached bitmap and renders dynamic markers.
- **Dynamic Indicators**:
  - Player blip: Pulsating cyan aura + white core + directional heading notch.
  - Waypoints: Active emerald/cyan circle icon with 1px border.
  - Boss Gate: Reactive locked red (`#ef4444`) vs unlocked/breached emerald (`#10b981`).
  - POIs: Subtle gold dots (`#fbbf24`), strictly masked when tile is unexplored.
- **Battery Optimization**: 30 Hz ($33.3\text{ms}$) throttled update filter during 120 FPS game loop (saving $75\%$ CPU draw calls), plus $100\%$ freeze when `window.isGamePaused = true`.
- **Strict Line Budget Compliance**:
  - `minimap_hud.js`: **252 lines** (Target $\le 280$ lines, Soft Cap 350, Hard Cap 500).
  - `index.html`: **199 lines** (Soft Cap $\le 200$ lines, Hard Cap 400).
- **Test Harness**: 14 automated unit tests created in `proposed_test_fog_and_minimap.py`, executing with **14/14 PASS (0.25s)**.

---

## 2. Minimap HUD Component Architecture (`minimap_hud.js`)

### 2.1. Coordinate Projection Mathematics
The client tile map is structured as a row-major grid with dimensions $(W, H)$ where $W \in [60, 120]$ and $H \in [45, 90]$. The minimap canvas dimensions are $C_W = 120\text{px}, C_H = 80\text{px}$ with a border padding $p = 2\text{px}$.

To prevent distortion across non-square aspect ratios ($4:3$ vs $3:2$), the uniform scale factor $S$ and centering offsets $(P_x, P_y)$ are computed as:

$$\text{avail}_W = C_W - 2p = 116\text{px}$$
$$\text{avail}_H = C_H - 2p = 76\text{px}$$
$$S = \min\left(\frac{\text{avail}_W}{W}, \frac{\text{avail}_H}{H}\right)$$
$$P_x = p + \frac{\text{avail}_W - W \cdot S}{2}$$
$$P_y = p + \frac{\text{avail}_H - H \cdot S}{2}$$

For any world coordinate $(w_x, w_y)$ (where $1\text{ world unit} = 1\text{ tile}$), the screen-space minimap coordinate $(m_x, m_y)$ is given by:

$$m_x = P_x + w_x \cdot S$$
$$m_y = P_y + w_y \cdot S$$

#### Projection Verification Examples:
| Map Dimensions | Aspect Ratio | Scale $S$ | Pixel Size $(W \cdot S \times H \cdot S)$ | Offset $(P_x, P_y)$ | Centering Behavior |
| :--- | :---: | :---: | :---: | :---: | :--- |
| **$60 \times 45$** (Zone 1) | $4:3$ ($1.33$) | $1.6889$ | $101.33 \times 76.00\text{px}$ | $(9.33, 2.00)\text{px}$ | Centered horizontally with $9.33\text{px}$ pillarbox |
| **$120 \times 90$** (Zone 10) | $4:3$ ($1.33$) | $0.8444$ | $101.33 \times 76.00\text{px}$ | $(9.33, 2.00)\text{px}$ | Uniform scaling, zero boundary clipping |
| **$64 \times 64$** (Square) | $1:1$ ($1.00$) | $1.1875$ | $76.00 \times 76.00\text{px}$ | $(22.00, 2.00)\text{px}$ | Symmetric horizontal pillarbox |
| **$100 \times 50$** (Wide) | $2:1$ ($2.00$) | $1.1600$ | $116.00 \times 58.00\text{px}$ | $(2.00, 11.00)\text{px}$ | Symmetric vertical letterbox |

Every coordinate $(tx, ty) \in [0, W-1] \times [0, H-1]$ maps strictly within the canvas interior $[2, 118] \times [2, 78]$.

---

### 2.2. Dual-Layer Offscreen Caching Architecture

Re-rendering thousands of tile rects every frame at 120 FPS drains mobile GPU/CPU cycles. The Minimap HUD implements a dual-layer caching pipeline:

```
[Window Event / Player Step]
           │
           ▼
[WarFog.updatePlayerVision] ──> (sets fogDirty = true)
           │
           ▼
[MinimapHUD.update(now)]
   ├── Check isGamePaused ────> IF paused: EARLY RETURN (0 draw calls)
   ├── Check throttleMs (33ms) ──> IF < 33ms: EARLY RETURN (caps at 30 Hz)
   └── IF (mapDirty || fogDirty):
           │
           ▼
      [MinimapHUD.bakeTerrain()] (OffscreenCanvas 120x80)
      ├── Solid Dark Backdrop (#08090c)
      ├── Map Frame (#0f1117)
      ├── Iterate tiles:
      │     fState == 0: SKIP (remains dark abyss)
      │     fState == 1: getMinimapTileColor(type, biome, isFogged=true)
      │     fState == 2: getMinimapTileColor(type, biome, isFogged=false)
      └── Reset dirty flags (mapDirty = false, fogDirty = false)
           │
           ▼
      [MinimapHUD.render()] (Onscreen Canvas 120x80)
      ├── ctx.clearRect(0, 0, 120, 80)
      ├── ctx.drawImage(baseCanvas, 0, 0)  <-- Single Blit!
      └── renderIndicators(ctx)           <-- ~5 Lightweight vector draws!
```

---

### 2.3. Terrain & Fog Palette Matrix

To harmonize with `tile_map_renderer.js` and grimdark aesthetic standards, the 20 canonical tile types are color-mapped:

| Tile Code | Tile Type | Visible Palette Color | Fogged Dimmed Color (`fState=1`) | Unexplored (`fState=0`) |
| :---: | :--- | :--- | :--- | :--- |
| `0` | `VOID` | `#08090c` (Dark Abyss) | `#08090c` | Masked `#08090c` |
| `1` | `FLOOR` | Biome Floor (e.g. `#3a3630`) | `#1c1e24` (Desaturated Gray) | Masked `#08090c` |
| `2` | `WALL` | Biome Wall (e.g. `#272522`) | `#18191c` (Dark Stone) | Masked `#08090c` |
| `3` | `BARRICADE` | `#78350f` (Wood Bone Amber) | `#2e1708` | Masked `#08090c` |
| `4` | `MUD_POOL` | `#3b2716` (Mud Brown) | `#1a110a` | Masked `#08090c` |
| `5` | `SPIKE_TRAP` | `#475569` (Slate Trap) | `#1e242d` | Masked `#08090c` |
| `6` | `CRUMBLED_DEBRIS` | `#64748b` (Debris Gray) | `#232a33` | Masked `#08090c` |
| `7` | `BONE_PILE` | `#e2e8f0` (Bone White) | `#4b5563` | Masked `#08090c` |
| `8` | `POISON_VENT` | `#166534` (Poison Green) | `#0c2b18` | Masked `#08090c` |
| `9` | `CHASM` | `#09090b` (Deep Void) | `#09090b` | Masked `#08090c` |
| `10` | `BOSS_GATE` | `#7e22ce` (Locked Purple) | `#2e104d` | Masked `#08090c` |
| `11` | `BOSS_ALTAR` | `#581c87` (Altar Violet) | `#240a38` | Masked `#08090c` |
| `12` | `RUNIC_FLOOR` | `#4338ca` (Runic Indigo) | `#1c1752` | Masked `#08090c` |
| `13` | `PATH` | Biome Path (e.g. `#6b5c4c`) | `#1c1e24` | Masked `#08090c` |
| `14` | `DENSE_TERRAIN` | `#14532d` (Dense Thicket) | `#0b2113` | Masked `#08090c` |
| `15` | `POI` | `#d97706` (Amber Landmark) | `#452608` | Masked `#08090c` |
| `16-18` | `ENCOUNTER_*` | `#44281d` / `#571f1b` / `#66181f` | Dimmed Red-Brown | Masked `#08090c` |
| `19` | `WATER` | `#0369a1` (Deep Water) | `#032d45` | Masked `#08090c` |

---

### 2.4. Dynamic Indicator Specifications

```
                     [Heading Pointer]
                       / (length: 5px)
                      /
                   (p.x, p.y)
                 ┌───────────┐
                 │  ● Core   │  radius: 2.2px (#ffffff)
                 │ ( ) Aura  │  radius: 4.0px + sin(t*6)*1.0 (rgba(56,189,248,0.35))
                 └───────────┘
```

1. **Player Position Blip**:
   - Location: computed via `worldToMinimap(player.wx, player.wy)`.
   - Core: Solid white circle (`#ffffff`, radius $2.2\text{px}$).
   - Aura: Pulsating cyan ring (`rgba(56, 189, 248, 0.35)`, radius $4.0\text{px} + \sin(\text{now} \cdot 0.006) \cdot 1.0\text{px}$).
   - Orientation Notch: Line vector extending $5.0\text{px}$ from player center at angle `facingAngle` in `#38bdf8` with $1.2\text{px}$ width.
2. **Waypoints**:
   - Location: `worldToMinimap(spawn.x, spawn.y)`.
   - Visual: Active emerald dot (`#10b981`, radius $2.5\text{px}$) with an outer $0.8\text{px}$ white stroke.
3. **Boss Gate**:
   - Location: `worldToMinimap(bossGate.x, bossGate.y)`.
   - Reactive State:
     - Sealed / Locked: Red circle (`#ef4444`, radius $3.0\text{px}$) with $1.0\text{px}$ translucent red boundary.
     - Unlocked / Breached: Emerald circle (`#10b981`, radius $2.5\text{px}$).
4. **Point of Interest (POI) Markers**:
   - Locations: array of `{ x, y, type }` from `currentMapMetadata.pois`.
   - Anti-Maphack Guard: Check `fogGrid[poi.y * W + poi.x] > 0`. If tile is `UNEXPLORED`, the marker is suppressed. If `EXPLORED_FOGGED` or `VISIBLE`, rendered as a gold dot (`#fbbf24`, radius $2.0\text{px}$).

---

### 2.5. Mobile Battery Optimization & Throttling
- **Throttling Interval**: During the 120 FPS render loop ($8.33\text{ms}$ per tick), `MinimapHUD.update()` enforces `if (now - this.lastRenderTime < 33) return;`. This restricts updates to $\le 30\text{ Hz}$.
- **Draw Call Reduction**:
  - Without throttling: $120 \text{ frames/sec} \times 6 \text{ draw calls} = 720\text{ ops/sec}$.
  - With throttling: $30 \text{ frames/sec} \times 6 \text{ draw calls} = 180\text{ ops/sec}$ ($75\%$ reduction).
  - With offscreen caching: Terrain loop (10,800 iterations) occurs only on dirty events (e.g. stepping to new tile), reducing frame CPU time to $< 0.1\text{ms}$.
- **Game Pause Freezing**: When `window.isGamePaused = true`, `update()` returns immediately without executing canvas transforms.

---

## 3. DOM & CSS Integration (`index.html`)

### 3.1. Mounting Location & Layout
In `client/webapp/index.html`, `#minimap-container` is mounted inside `<div id="app-viewport">` right below `<header>`:

```html
<!-- TOP-RIGHT HUD MINIMAP (120x80 PoE2 Procedural HUD) -->
<div id="minimap-container" class="pointer-events-auto absolute top-12 right-3 z-30 flex flex-col items-center bg-stone-950/85 backdrop-blur-md border border-stone-800/80 rounded-lg p-1 shadow-2xl cursor-pointer select-none" data-tooltip-title="Bản Đồ Thu Nhỏ" data-tooltip-desc="Bản đồ địa hình hoang vực, nhấp để mở Toàn Cảnh." data-tooltip-hotkey="M"><div class="flex items-center justify-between w-full px-1 mb-0.5 text-[8px] font-mono text-stone-400"><span id="minimap-zone-label" class="text-amber-400/90 font-semibold truncate max-w-[85px]">Táng Kiếm Nhai</span><span id="minimap-coords" class="text-stone-500 font-mono">0,0</span></div><canvas id="minimap-canvas" width="120" height="80" class="block rounded border border-stone-900 bg-stone-950/90 shadow-inner"></canvas></div>
```

### 3.2. Viewport Allocation & Mobile Ergonomics
On an iPhone screen ($390 \times 844\text{px}$ portrait or $844 \times 390\text{px}$ landscape):
- Outer container width: $120\text{px} + 2 \times 4\text{px} \text{ padding} = 128\text{px}$.
- Viewport width ratio: $128 / 390 = 32.8\%$.
- Leaves $262\text{px}$ ($67.2\%$) of top screen space completely unobstructed for player status cards, telemetry pills, and dynamic popups.
- Tapping `#minimap-container` triggers `window.toggleWorldMapModal?.()`.

### 3.3. Strict Line Budget Compliance
- Current `index.html` line count: **198 lines**.
- Proposed integration:
  - Remove 1 redundant blank line (line 89).
  - Insert 2-line compacted `#minimap-container`.
  - Add `<script type="module" src="js/ui/minimap_hud.js"></script>` to line 194.
- Resulting `index.html` line count: **199 lines** ($\le 200$ Soft Cap, well under 400 Hard Cap).

---

## 4. Test Specification & Verification Results

A dedicated unit test suite (`proposed_test_fog_and_minimap.py`) was constructed and executed with `pytest`:

```bash
pytest .agents/teamwork/explorer_m4_3/proposed_test_fog_and_minimap.py -v
```

### Execution Log:
```
collecting ... collected 14 items

proposed_test_fog_and_minimap.py::TestMinimapProjection::test_aspect_ratio_preservation_standard_wilderness PASSED [  7%]
proposed_test_fog_and_minimap.py::TestMinimapProjection::test_aspect_ratio_preservation_large_zone PASSED [ 14%]
proposed_test_fog_and_minimap.py::TestMinimapProjection::test_coordinate_mapping_accuracy PASSED [ 21%]
proposed_test_fog_and_minimap.py::TestFogOfWarStateMachine::test_initial_state_unexplored PASSED [ 28%]
proposed_test_fog_and_minimap.py::TestFogOfWarStateMachine::test_reveal_radius_8_tiles PASSED [ 35%]
proposed_test_fog_and_minimap.py::TestFogOfWarStateMachine::test_visible_to_explored_fogged_transition PASSED [ 42%]
proposed_test_fog_and_minimap.py::TestFogOfWarStateMachine::test_persistence_key_and_integrity PASSED [ 50%]
proposed_test_fog_and_minimap.py::TestDynamicIndicators::test_player_heading_vector_calculation PASSED [ 57%]
proposed_test_fog_and_minimap.py::TestDynamicIndicators::test_boss_gate_marker_color_states PASSED [ 64%]
proposed_test_fog_and_minimap.py::TestDynamicIndicators::test_poi_marker_fog_masking PASSED [ 71%]
proposed_test_fog_and_minimap.py::TestBatteryThrottling::test_30hz_throttling_filters_120fps PASSED [ 78%]
proposed_test_fog_and_minimap.py::TestBatteryThrottling::test_pause_game_suppression PASSED [ 85%]
proposed_test_fog_and_minimap.py::TestCodeHygieneBudget::test_proposed_minimap_hud_line_cap PASSED [ 92%]
proposed_test_fog_and_minimap.py::TestCodeHygieneBudget::test_index_html_current_line_cap PASSED [100%]

============================= 14 passed in 0.25s ==============================
```

All 14 unit test criteria verified with 100% pass rate.

---

## 5. Downstream Worker Implementation Checklist

For Worker M4 (`worker_m4`):
1. **Source Creation**:
   - Copy `proposed_minimap_hud.js` to `client/webapp/js/ui/minimap_hud.js` (252 lines $\le 280$).
2. **DOM Mounting**:
   - Apply `proposed_index_patch.diff` to `client/webapp/index.html` (199 lines $\le 200$).
3. **Test Integration**:
   - Copy `proposed_test_fog_and_minimap.py` to `tests/unit/test_fog_and_minimap.py`.
4. **Game Loop Wiring**:
   - In `client/webapp/js/engine/canvas_renderer.js` line 320, insert:
     ```javascript
     if (typeof window.MinimapHUD !== 'undefined' && typeof window.MinimapHUD.update === 'function' && typeof player !== 'undefined') {
       window.MinimapHUD.update(now, player.wx, player.wy, player.facingAngle || 0);
     }
     ```
   - In `client/webapp/js/engine/tile_grid_loader.js` line 89, invoke `window.MinimapHUD?.setMap(...)`.
5. **Regression Verification**:
   - Execute `pytest tests/unit/test_fog_and_minimap.py tests/unit/test_tile_collision.py tests/e2e/test_poe2_map_system_e2e.py -q`.
   - Run `python tools/lint/check_code_and_doc_hygiene.py --strict`.
