# FREEEXILE: SPECIFICATION MINING REPORT
## POE2 PROCEDURAL TILE MAP SYSTEM OVERHAUL

> **Document ID**: SPEC-MAP-2026.1  
> **Status**: APPROVED TECHNICAL SPECIFICATION  
> **Author**: Map Spec Miner Subagent  
> **Authoritative Sources**:
> - `c:\Projects\FreeExile\.agents\teamwork\ORIGINAL_REQUEST.md` (§ `## 2026-10-01T19:19:13Z`, `## 2026-10-01T11:33:27Z`)
> - `c:\Projects\FreeExile\GEMINI.md` & `c:\Projects\FreeExile\AGENTS.md`
> - `c:\Projects\FreeExile\docs\standards\POE2_GAMEPLAY_AND_ZONE_ARCHITECTURE.md`
> - `c:\Projects\FreeExile\docs\standards\ENGINEERING_STANDARDS_2026.md`
> - `c:\Projects\FreeExile\server\world\map_data_types.py`
> - `c:\Projects\FreeExile\server\world\procedural_map_engine.py`
> - `c:\Projects\FreeExile\server\world\zone_catalog.py`
> - `c:\Projects\FreeExile\server\world\zone_engine.py`
> - `c:\Projects\FreeExile\client\webapp\js\engine\world_renderer.js`
> - `c:\Projects\FreeExile\client\webapp\js\engine\collision_engine.js`
> - `c:\Projects\FreeExile\client\webapp\js\engine\monster_system.js`
> - `c:\Projects\FreeExile\client\webapp\js\engine\canvas_renderer.js`
> - `c:\Projects\FreeExile\client\webapp\js\data\wilderness_zone_packs.js`

---

## 1. Executive Technical Summary

The FreeExile map architecture transitions from a static mathematical background canvas (`world_renderer.js` hardcoded formulas) to a **Server-Authoritative Procedural Tile Map Pipeline (PoE2 Standard)**.
The system guarantees:
1. Dynamic replayability per zone entry based on `(zone_id, seed, biome_id)`.
2. Binary-compact network serialization via `Uint8Array` (1 byte per tile).
3. 120 FPS ProMotion mobile rendering via **Isometric Viewport Culling** and **16×16 OffscreenCanvas Chunk Pre-rendering**.
4. RAM budget compliance ($\le 8\text{ MB}$ total tile + chunk bitmap memory).
5. Tile-level collision with smooth 2-axis wall sliding, grid-aware monster AI pathfinding (A*/BFS), and authoritative Boss Gate sealing.
6. Multi-state Fog of War (`0=UNEXPLORED`, `1=EXPLORED_FOGGED`, `2=VISIBLE`) with `localStorage` persistence and dedicated 120×80 HUD Minimap.
7. Terrain-anchored monster encounters with dynamic pack distribution, interactive POI wave ambushes, and lore popups.
8. Zero regression on all 894 unit tests, waypoint safe radius, and pause game state.

---

## 2. Technical Domain Specifications & Interface Contracts

### 2.1. Tile Types & Binary Protocol (Uint8Array)

The unified `TileType` enumeration integrates existing dungeon generator obstacle types and new open-world terrain attributes into a 1-byte unsigned integer range (`0` to `255`):

```python
class TileType(Enum):
    VOID = 0                 # Empty void / out-of-bounds (Impassable, blocks vision)
    FLOOR = 1                # Standard walkable ground (Passable, cost 1.0)
    WALL = 2                 # Impassable solid cliff/rock (Impassable, blocks vision)
    DESTRUCTIBLE_BARRICADE = 3 # Destructible chokepoint obstacle (HP gated)
    MUD_POOL = 4             # Quagmire hazard (Passable, movement cost 2.0 / slow -50%)
    SPIKE_TRAP = 5           # Hidden spike hazard (Passable, damage 25 on step)
    CRUMBLED_DEBRIS = 6      # Rubble obstruction (Passable, movement cost 1.5)
    BONE_PILE = 7            # Tactical bone cluster
    POISON_VENT = 8          # Toxic vent hazard
    CHASM = 9                # Deep abyssal chasm (Impassable, blocks vision)
    BOSS_GATE = 10           # Boss seal barrier (Impassable until encounter threshold met)
    BOSS_ALTAR = 11          # Central ritual dais in Boss Arena
    RUNIC_FLOOR = 12         # Sacred floor surrounding Boss Altar
    PATH = 13                # Cobble/beaten path (Passable, +20% movement speed, cost 0.8)
    DENSE_TERRAIN = 14       # Thick briars/roots (Passable, -30% movement speed, cost 1.43)
    POI = 15                 # Point of Interest / Shrine / NPC / Ambush trigger
    ENCOUNTER_LOW = 16       # Low-density monster pack territory
    ENCOUNTER_MEDIUM = 17    # Medium-density monster pack territory
    ENCOUNTER_HIGH = 18      # High-density elite monster pack territory
    WATER = 19               # Deep water hazard (Impassable)
```

**Binary Serialization Contract**:
- Grid Dimensions: Width $W$, Height $H$.
- Array Layout: Row-major contiguous buffer `Uint8Array(W * H)`.
- Indexing Formula: `index = y * W + x`.
- Client Global Handle: `window.currentMapGrid` (`Uint8Array`), `window.currentMapWidth` ($W$), `window.currentMapHeight` ($H$).
- Coordinate Lookup API: `getTileAt(tx: number, ty: number): number`. Returns `TileType.WALL` (2) if `tx < 0 || tx >= W || ty < 0 || ty >= H`.

### 2.2. Zone Scaling Matrix

| Canonical Zone ID | Environment | Min Level | Min Grid ($W \times H$) | Chunks ($16 \times 16$) | Biome ID |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `zone_boundless_sanctuary` | Sanctuary Hub | Lv.1 | $60 \times 45$ (Safe) | $4 \times 3 = 12$ | `BLEACHED_BONE_CANYON` |
| `zone_player_hideout` | Hideout | Lv.1 | $60 \times 45$ (Safe) | $4 \times 3 = 12$ | `BLEACHED_BONE_CANYON` |
| `zone_tang_kiem_nhai` | Bone Strand | Lv.1–10 | $60 \times 45$ | $4 \times 3 = 12$ | `BLEACHED_BONE_CANYON` |
| `zone_ancient_sword_barrow`| Rotten Swamp | Lv.8–20 | $68 \times 52$ | $5 \times 4 = 20$ | `SAVAGE_MANGROVE_SWAMP` |
| `zone_boundless_sandstorm` | Bone Wastes | Lv.20–30 | $76 \times 58$ | $5 \times 4 = 20$ | `BLEACHED_BONE_CANYON` |
| `zone_blood_scale_ruins`   | Slaughter Ruins | Lv.30–40 | $84 \times 64$ | $6 \times 4 = 24$ | `CORRUPTED_FIEND_RUINS` |
| `zone_five_elements_altar` | Primal Altar | Lv.40–50 | $92 \times 70$ | $6 \times 5 = 30$ | `CRIMSON_BLOOD_FOREST` |
| `zone_abyssal_ice_pond`    | Abyssal Mire | Lv.50–60 | $100 \times 76$ | $7 \times 5 = 35$ | `SAVAGE_MANGROVE_SWAMP` |
| `zone_infinite_blood_rift` | Starved Gorge | Lv.60–70 | $108 \times 82$ | $7 \times 6 = 42$ | `CRIMSON_BLOOD_FOREST` |
| `zone_purgatory_lava_cavern`| Blood Caldera | Lv.70–85 | $114 \times 86$ | $8 \times 6 = 48$ | `OUTCAST_MINE_SHAFTS` |
| `zone_boundless_celestial_palace` | Apex | Lv.85–100 | $120 \times 90$ | $8 \times 6 = 48$ | `CORRUPTED_FIEND_RUINS` |

---

## 3. Features Discovered

| # | Category | Feature | Description | Inputs | Outputs | Error Behavior | Discovered Via |
|---|----------|---------|-------------|--------|---------|----------------|----------------|
| 1 | R1 Pipeline | Seed-based Procedural Map Generation | Synthesizes organic wilderness map layouts from seed, zone_id, and biome config with rooms, winding corridors, and chokepoints. | `zone_id: str, seed: int, biome_id: str` | `MapGridData` object containing `tiles`, `obstacles`, `rooms`, `spawn_point`, `boss_point` | Raises `ValueError` if `biome_id` not found in `MAP_BIOMES`. | `server/world/procedural_map_engine.py:33`, `ORIGINAL_REQUEST.md:204` |
| 2 | R1 Pipeline | Compact Uint8Array Serialization | Encodes the 2D tile cell grid into a contiguous flat 1-byte-per-cell `Uint8Array` in row-major order. | `map_data.tiles: List[List[TileCell]]` | `bytes` or `bytearray` of length $W \times H$ | Raises `TypeError` or truncation if cell value exceeds 255. | `ORIGINAL_REQUEST.md:206`, `server/world/map_data_types.py:102` |
| 3 | R1 Pipeline | Spawn Safe Area Floor Guarantee | Guarantees all tiles within Euclidean radius 8.0 around `spawn_point` are carved as walkable `FLOOR` with zero wall traps. | `tiles: 2D array, spawn_point: (sx, sy), radius: 8` | Mutated `tiles` with cleared spawn clearing | If spawn is near grid perimeter, clips clearing strictly within bounds $[1, W-2]$. | `ORIGINAL_REQUEST.md:213`, `server/world/procedural_map_engine.py:89` |
| 4 | R1 Pipeline | Guaranteed Sinuous Main Path | Enforces at least 1 non-linear path from spawn to boss gate with sinuosity $\ge 1.25$ and tactical junctions. | `start: (sx, sy), target: (gx, gy)` | Path list `List[(x, y)]` & sinuosity ratio $\ge 1.25$ | Returns `(False, [])` if no valid corridor connects start and target. | `server/world/procedural_map_engine.py:342`, `ORIGINAL_REQUEST.md:208` |
| 5 | R1 Pipeline | Encounter Zones Classification | Carves and tags 2–3 dense encounter zones along main path branches as `ENCOUNTER_LOW`, `ENCOUNTER_MEDIUM`, `ENCOUNTER_HIGH`. | `rooms: List[MapRoom], path: List[(x, y)]` | Tagged tile regions in `MapGridData` | Reverts to `FLOOR` if room overlap invalid. | `ORIGINAL_REQUEST.md:209`, `server/world/map_data_types.py:13` |
| 6 | R1 Pipeline | Rewarded Dead-End Carving | Generates 1–2 branching dead-ends containing mini-event or loot cache rewards, secluded from the Boss Arena. | `rooms: List[MapRoom], rng: Random, count: 2` | `dead_end_points: List[(x, y)]` | Dead ends terminated if approaching Boss Arena perimeter. | `server/world/procedural_map_engine.py:216`, `ORIGINAL_REQUEST.md:210` |
| 7 | R1 Pipeline | Isolated Boss Gate & Sealed Arena | Places exactly 1 `BOSS_GATE` tile on the Boss Arena perimeter; all other perimeter cells sealed as solid `WALL`. | `boss_room: MapRoom, normal_rooms: List[MapRoom]` | `boss_gate_coord: (gx, gy)` | Gate unreachable from exterior until breach event triggered. | `server/world/procedural_map_engine.py:177`, `test_war_fog_and_procedural_map.py:380` |
| 8 | R1 Pipeline | Point of Interest (POI) Placement | Distributes 1–3 POI tiles (`POI = 15`) across branching rooms for checkpoints, quest objectives, and wave ambushes. | `normal_rooms: List[MapRoom], count: 1..3` | `poi_points: List[(x, y, type)]` | Ensures POI is never placed in spawn room or boss room. | `ORIGINAL_REQUEST.md:212`, `server/world/map_data_types.py:112` |
| 9 | R1 Pipeline | Client Map Grid Ingestion | Receives binary buffer via WebSocket or client fallback, saves to `window.currentMapGrid`, exposes `getTileAt(tx, ty)`. | Binary `Uint8Array`, width, height | `window.currentMapGrid`, `window.getTileAt` | Returns `TileType.WALL` (2) if out-of-bounds coordinates passed. | `ORIGINAL_REQUEST.md:215`, `client/webapp/js/ui/worldmap_dungeons.js:96` |
| 10 | R2 Rendering | Viewport Frustum Culling | Converts screen corners via `isoToWorld` with 2-tile padding to determine active bounding tile coordinates $[tileMinX..tileMaxX, tileMinY..tileMaxY]$. | `camera.wx, camera.wy, viewport.clientWidth, viewport.clientHeight` | Bounding box: `tileMinX, tileMaxX, tileMinY, tileMaxY` | Clamps coordinates within $[0, W-1]$ and $[0, H-1]$. | `ORIGINAL_REQUEST.md:221`, `client/webapp/js/engine/world_renderer.js:16` |
| 11 | R2 Rendering | Draw Call Budget Enforcement | Restricts render pass draw calls to $\le (viewport_W / TILE_W + 5) \times (viewport_H / TILE_H + 5)$. | Canvas dimensions, `TILE_W=64, TILE_H=32` | Max draw call counter limit enforcement | Throttles or skips off-screen chunks if budget threatened. | `ORIGINAL_REQUEST.md:221`, `client/webapp/js/engine/world_renderer.js:20` |
| 12 | R2 Rendering | 16x16 Chunk OffscreenCanvas Pre-rendering | Partitions tile map into $16 \times 16$ tile chunks, pre-rendering static terrain into dedicated `OffscreenCanvas` bitmaps. | `chunkX: int, chunkY: int, tiles: Uint8Array` | Cached `OffscreenCanvas` / `ImageBitmap` per chunk | Falls back to standard canvas element if `OffscreenCanvas` unsupported. | `ORIGINAL_REQUEST.md:222`, `client/src/render/IsometricMapRenderer.ts:41` |
| 13 | R2 Rendering | Chunk Dirty Flag Mutation | Re-renders an `OffscreenCanvas` chunk only when an internal tile changes state (gate opened, obstacle broken). Clean chunks are reused. | `chunk.dirty: bool, tx: int, ty: int` | Invalidation of specific chunk canvas | Ignored if target chunk index is out of range. | `ORIGINAL_REQUEST.md:223`, `server/world/procedural_map_engine.py:326` |
| 14 | R2 Rendering | Unified Tile Sprite Sheet Atlas | Renders all isometric tile types using a unified sprite atlas texture with procedural fallback colors. | `tile_type: int, atlasImg: HTMLImageElement` | Texture sub-rectangle coordinates $(sx, sy, sw, sh)$ | Uses solid fill color matching biome palette if atlas asset missing. | `ORIGINAL_REQUEST.md:224`, `client/webapp/js/engine/world_renderer.js:21` |
| 15 | R2 Rendering | Mobile RAM Budget Compliance ($\le 8\text{ MB}$) | Limits memory allocated by tile buffers and active chunk canvases to $\le 8\text{ MB}$ on $120 \times 90$ maps. | Active canvas chunks, `Uint8Array` buffers | Memory consumption verified via `performance.memory` | Releases off-screen non-dirty chunk buffers if RAM threshold approached. | `ORIGINAL_REQUEST.md:225`, `GEMINI.md:§2.2` |
| 16 | R2 Rendering | Legacy World Renderer Math Deprecation | Fully replaces hardcoded mathematical tile formula (`Math.abs(x-y) <= 1`) with data-driven tile map renderer. | Active zone grid data | Clean isometric visual terrain | Fallback to ambient color fill if grid not yet initialized. | `ORIGINAL_REQUEST.md:226`, `client/webapp/js/engine/world_renderer.js:12-28` |
| 17 | R3 Collision | Tile-Level Blocked Position Check | Checks if world coordinate $(wx, wy)$ with radius penetrates `WALL`, `CHASM`, `WATER`, or locked `BOSS_GATE`. | `wx: float, wy: float, radius: float, isDodge: bool` | `boolean` (true if blocked, false if walkable) | Out-of-bounds coordinates return `true` (blocked). | `ORIGINAL_REQUEST.md:231`, `client/webapp/js/engine/collision_engine.js:88` |
| 18 | R3 Collision | Dual-Axis Smooth Wall Sliding | Retains 2-axis sliding decomposition (X-then-Y) to allow characters to glide smoothly along diagonal walls without sticking. | `curWx, curWy, moveDistX, moveDistY, radius` | `{ wx, wy, isSliding: bool }` | Halts movement if both single-axis components are blocked. | `ORIGINAL_REQUEST.md:233`, `client/webapp/js/engine/collision_engine.js:148` |
| 19 | R3 Collision | Terrain Speed Modifiers | Applies $+20\%$ velocity bonus on `PATH` tiles and $-30\%$ velocity penalty on `DENSE_TERRAIN` and `MUD_POOL`. | `player.wx, player.wy, baseSpeed: float` | Modified effective player speed | Resets to normal speed immediately upon exiting modified terrain. | `ORIGINAL_REQUEST.md:214`, `server/world/map_data_types.py:52` |
| 20 | R3 Collision | Monster AI Grid Pathfinding | Monsters chasing player navigate around `WALL` tiles using grid pathfinding (BFS / A*) within encounter bounds. | `monster.wx/wy, player.wx/wy, grid: Uint8Array` | Next tile step waypoint for monster entity | Falls back to leashing or straight line if target in direct line-of-sight. | `ORIGINAL_REQUEST.md:234`, `client/webapp/js/engine/monster_system.js:250` |
| 21 | R3 Collision | Boss Gate Condition & Breach Event | Boss Gate blocks passage until required monster pack kill count is achieved; unlocking turns tile to `FLOOR` and sets chunk dirty. | `zone_id: str, kills: int, threshold: int` | Mutated tile to `FLOOR`, `chunk.dirty = true` | Prevents gate opening if kill count is below required quota. | `ORIGINAL_REQUEST.md:235`, `server/world/procedural_map_engine.py:330` |
| 22 | R3 Collision | Boss Gate Visual Seal Rune | Renders a rotating glowing purple/gold seal rune at the `BOSS_GATE` tile while locked; dissipates when breached. | `ctx, gateWx, gateWy, isBreached: bool, time: float` | Animated canvas isometric rune circle | Stops rendering rune once `isBreached === true`. | `ORIGINAL_REQUEST.md:235`, `client/webapp/js/engine/world_renderer.js:162` |
| 23 | R3 Collision | Server-Side Spawn Validation | `zone_engine.py` validates monster spawn coordinates against tile grid; rejects spawns inside walls or chasms. | `zone_id: str, is_dummy: bool, x: float, y: float` | `(bool, str)` validation tuple | Rejects with descriptive reason if target tile is non-walkable. | `ORIGINAL_REQUEST.md:236`, `server/world/zone_engine.py:126` |
| 24 | R4 Fog & HUD | Client-Side Fog of War Matrix | Maintains `window.fogGrid` (`Uint8Array` of size $W \times H$) with states `0=UNEXPLORED`, `1=EXPLORED_FOGGED`, `2=VISIBLE`. | Tile map width, height | `window.fogGrid` (`Uint8Array`) | Initializes to all `0` (UNEXPLORED) if no saved state found. | `ORIGINAL_REQUEST.md:240`, `server/world/map_data_types.py:29` |
| 25 | R4 Fog & HUD | 8-Tile Radius Fog Reveal | Updates fog each frame: tiles within 8-tile radius of player become `VISIBLE` (2); previously seen tiles demoted to `EXPLORED_FOGGED` (1). | `player.wx, player.wy, radius: 8` | Updated `window.fogGrid` state | Raycast stops when striking vision-blocking `WALL` or `BOSS_GATE`. | `ORIGINAL_REQUEST.md:241`, `client/webapp/js/ui/war_fog.js:65` |
| 26 | R4 Fog & HUD | Fog State Persistence in LocalStorage | Persists explored fog matrix in browser `localStorage` keyed by `${zone_id}_${seed}` so fog does not reset on portal return. | `zone_id: str, seed: int, fogGrid: Uint8Array` | Saved/loaded string in `localStorage` | Gracefully falls back to fresh fog if storage full or corrupted. | `ORIGINAL_REQUEST.md:240`, `client/webapp/js/ui/war_fog.js:43` |
| 27 | R4 Fog & HUD | Fog Overlay Canvas Rendering | Renders solid black for `UNEXPLORED`, `rgba(0,0,0,0.55)` for `EXPLORED_FOGGED`, and transparent for `VISIBLE`. | Canvas context, `fogGrid: Uint8Array` | Visual lighting & fog shroud overlay | Uses offscreen fog canvas cache; re-bakes only when fog changes. | `ORIGINAL_REQUEST.md:242`, `client/webapp/js/ui/war_fog_renderer.js:30` |
| 28 | R4 Fog & HUD | Dedicated 120x80 Minimap HUD Canvas | Displays mini-radar canvas ($120 \times 80\text{px}$) in top-right HUD with explored terrain, player, waypoints, boss gate, and POI markers. | `fogGrid, playerPos, waypoints, bossGate, poiPoints` | Rendered 2D minimap HUD element | Re-renders only on tile movement or fog matrix mutation. | `ORIGINAL_REQUEST.md:244`, `client/webapp/index.html:32` |
| 29 | R4 Fog & HUD | Minimap Marker Color Encoding | Encodes distinct markers: Explored=Biome color, Unexplored=Black, Player=White dot, Waypoint=Green dot, Boss Gate=Red dot, POI=Yellow dot. | Marker coordinates and types | Colored dots and icons on minimap | Clamps marker positions inside $120 \times 80$ canvas boundaries. | `ORIGINAL_REQUEST.md:245-250` |
| 30 | R5 Encounters | Terrain-Anchored Monster Pack Spawning | Randomizes monster pack spawn locations within designated `ENCOUNTER_*` tile clusters instead of fixed coordinates. | `pack_template, mapGrid: Uint8Array, encounterTier` | `activeMonsters` spawned at random valid encounter tiles | Re-rolls spawn coordinate if tile is blocked or inside safe radius. | `ORIGINAL_REQUEST.md:257`, `client/webapp/js/data/wilderness_zone_packs.js:9` |
| 31 | R5 Encounters | Scripted POI Wave Ambush | Stepping within 1 tile of an uncleared `POI` triggers a scripted monster ambush wave; clearing POI extinguishes glow and drops loot. | `player.wx/wy, poi.x/y, isCleared: bool` | Spawned ambush wave, visual glow toggle | No-op if POI is already cleared. | `ORIGINAL_REQUEST.md:259`, `client/webapp/js/engine/ambush_trigger_system.js:15` |
| 32 | R5 Encounters | Boss Gate Proximity Lore Popup | When player enters within 3 tiles of `BOSS_GATE`, displays floating lore popup with remaining kill quota requirement. | `player.wx/wy, gate.x/y, killedPacks, totalPacks` | Floating lore HUD prompt / modal | Suppressed after Boss Gate is successfully unlocked. | `ORIGINAL_REQUEST.md:260`, `client/webapp/js/ui/war_fog.js:158` |
| 33 | R5 Encounters | Zone Encounter Progress Tracker | `window.zoneEncounterProgress[zone_id]` records defeated packs and automatically unlocks the Boss Gate upon reaching threshold. | `monster_death_event, packId, zone_id` | Updated kill count, gate unlock trigger | Prevents double-counting minions from the same pack. | `ORIGINAL_REQUEST.md:261`, `client/webapp/js/engine/monster_system.js:179` |
| 34 | Regression | Unit Test Suite Preservation (894+ Tests) | Preserves complete compatibility across the entire existing test suite (all 894 unit tests continue to pass). | `pytest tests/unit/ -q` | 894 passed, 0 failed | Any test failure constitutes an immediate block. | `ORIGINAL_REQUEST.md:298`, `GEMINI.md:§4.1` |
| 35 | Regression | Waypoint Safe Radius Preservation | Preserves 8.0-unit immortal safe radius around waypoints, 2%/s healing, monster leashing, and spawn rejection. | `player.wx/wy, waypoint.wx/wy, radius: 8.0` | Healing active, monsters disengaged, visual rune ring | Monsters attacking player inside safe zone is strictly forbidden. | `ORIGINAL_REQUEST.md:299`, `server/world/zone_engine.py:113` |
| 36 | Regression | Pause Game Controller Preservation | Preserves game pause on ESC key and Settings modal open (`#btn-open-settings`); render loop draws static frame, skips ticks. | `ESC key, window.setGamePaused(bool)` | `window.isGamePaused = true`, `#overlay-pause` visible | Unpause immediately restores game loop ticks and hides overlay. | `ORIGINAL_REQUEST.md:300`, `client/webapp/js/engine/canvas_renderer.js:1-11` |

---

## 4. Edge Cases

| # | Feature | Input | Observed Behavior |
|---|---------|-------|-------------------|
| 1 | R1 Pipeline | Seed generating disconnected boss room candidate | ProceduralMapEngine A* corridor carver falls back to Bresenham line carving to guarantee 100% path connectivity from spawn to boss. |
| 2 | R1 Pipeline | Spawn point placed too close to grid edge ($x < 8$ or $y < 8$) | Spawn safe clearing (radius 8) is clamped to grid boundaries $[1, W-2]$, ensuring no index out-of-bounds error occurs. |
| 3 | R1 Pipeline | Unknown `zone_id` requested on client teleport | Defaults to `zone_tang_kiem_nhai` ($60 \times 45$ grid) with default seed to prevent client crash. |
| 4 | R2 Rendering | Viewport camera positioned at extreme corner of map | Frustum culling clamps `tileMinX, tileMinY` to 0 and `tileMaxX, tileMaxY` to $W-1, H-1$. Draws ambient background without rendering void artifacts. |
| 5 | R2 Rendering | Browser environment lacking `OffscreenCanvas` support | Seamlessly falls back to hidden `<canvas>` elements created via `document.createElement('canvas')`. |
| 6 | R2 Rendering | Mobile device with strict RAM constraints on $120 \times 90$ map | Pre-renders only visible chunks (e.g., 4–9 chunks) into canvas memory; off-screen clean chunks are not allocated until traversed. Memory remains $\le 8\text{ MB}$. |
| 7 | R3 Collision | Player executing dodge roll (`isIFrame = true`) into low obstacle vs solid wall | Dodge roll bypasses low obstacle footprints (`isLowObstacle = true`), but is strictly blocked by `WALL`, `CHASM`, `WATER`, and locked `BOSS_GATE`. |
| 8 | R3 Collision | Player moving diagonally into 90-degree corner of two walls | 2-axis sliding tests X and Y axes independently; if both blocked, movement is zeroed without jittering or clipping through the corner. |
| 9 | R3 Collision | Monster chasing player across an obstacle-choked corridor | Monster AI grid pathfinding computes shortest path around obstacles; if target unreachable, leashes back to spawn origin and regenerates HP. |
| 10 | R3 Collision | Server validates monster spawn directly on a `CHASM` or `WALL` tile | `zone_engine.py` `validate_monster_spawn` returns `(False, "Nghiêm cấm sản sinh quái vật trên địa hình vật cản...")`, aborting the spawn. |
| 11 | R4 Fog & HUD | Player teleports to another zone and returns | Explored fog state is reloaded from `localStorage` under key `fe_fog_${zone_id}_${seed}`; previously seen terrain remains `EXPLORED_FOGGED`. |
| 12 | R4 Fog & HUD | Browser `localStorage` quota exceeded when saving fog | Caught by `try...catch`; degrades gracefully to in-memory session fog without crashing the game. |
| 13 | R4 Fog & HUD | Minimap rendering for non-square grid ($120 \times 90$) | Aspect ratio scaling `scale = Math.min(120 / W, 80 / H)` preserves isometric proportion and centers the minimap in the $120 \times 80$ box. |
| 14 | R5 Encounters | Player triggers POI ambush while already engaged with an elite pack | Ambush monsters spawn around POI without de-spawning active pack; encounter progress tracks all packs independently. |
| 15 | R5 Encounters | Player stands on `BOSS_GATE` tile at the exact moment kill threshold is reached | Gate tile instantly mutates from `BOSS_GATE` to `FLOOR`; `isPositionBlocked` becomes `false`, allowing player to walk through smoothly. |
| 16 | Regression | Game paused (`isGamePaused === true`) while player is in safe radius | HP regeneration tick is suspended along with all combat and movement; static canvas continues to render with pause overlay visible. |

---

## 5. Architectural Constraints & Code Hygiene Gate

### 5.1. File Length Caps (Strict Directives from GEMINI.md & AGENTS.md)
- **Logic code files** (`.py`, `.js`, `.ts`): Soft Cap $\le 350$ lines, Hard Cap $\le 500$ lines.
- **Catalogs** (`*_catalog.py`, `*_catalog.js`): Soft Cap $\le 700$ lines, Hard Cap $\le 1000$ lines.
- **Documentation files** (`.md`): Soft Cap $\le 400$ lines, Hard Cap $\le 600$ lines.
- **Method/Function Length**: Hard Cap $\le 50$ lines, Cyclomatic complexity $\le 10$.

### 5.2. File Modification Strategy for Implementation Team
To comply with the 500-line hard cap on existing files that are already large:
- `client/webapp/js/engine/world_renderer.js` (currently 460 lines): Strip legacy lines 12–28 (`tile_grass`, `Math.abs(x-y) <= 1`). Delegate chunk cache and tile drawing to a dedicated modular helper `client/webapp/js/engine/tile_map_renderer.js` ($\le 300$ lines).
- `client/webapp/js/engine/monster_system.js` (currently 485 lines): Delegate grid pathfinding (A*/BFS) to `client/webapp/js/engine/grid_pathfinder.js` ($\le 200$ lines).
- `server/world/procedural_map_engine.py` (currently 395 lines): Modularize open-world generator helpers or encounter region taggers into `server/world/wilderness_map_generator.py` ($\le 300$ lines).

---

## 6. Verification and Acceptance Checklist

- [ ] **R1 Tile Pipeline**: Server generates $\ge 60 \times 45$ map for `zone_tang_kiem_nhai` and up to $120 \times 90$ for `zone_boundless_celestial_palace` with main path, encounter zones, boss gate, POIs, and dead ends.
- [ ] **R1 Serialization**: Grid serialized to `Uint8Array` (1 byte/tile) and loaded into `window.currentMapGrid`; `getTileAt(tx, ty)` returns exact tile types.
- [ ] **R2 Viewport Culling**: Frame draw calls $\le (viewport_W / TILE_W + 5) \times (viewport_H / TILE_H + 5)$.
- [ ] **R2 Chunk Cache**: $16 \times 16$ chunks cached in OffscreenCanvas; clean chunks not re-rendered on every frame.
- [ ] **R2 Benchmark**: `tools/perf/map_render_benchmark.js` executes with average FPS $\ge 30$ and memory $\le 8\text{ MB}$.
- [ ] **R3 Tile Collision**: `isPositionBlocked(wx, wy)` blocks `WALL`, `CHASM`, `WATER`, and locked `BOSS_GATE`; smooth wall sliding preserved.
- [ ] **R3 Unit Tests**: New test suite `tests/unit/test_tile_collision.py` contains $\ge 10$ unit tests; all pass.
- [ ] **R4 Fog of War**: Tiles outside view radius 8 are obscured (`0=UNEXPLORED` black, `1=EXPLORED_FOGGED` 55% tint); persisted in `localStorage`.
- [ ] **R4 Minimap**: $120 \times 80\text{px}$ canvas HUD shows biome terrain, player (white), waypoints (green), boss gate (red), POIs (yellow).
- [ ] **R5 Encounters**: Monster packs anchored to encounter tile regions; POI ambushes trigger on step; Boss Gate lore popup appears within 3 tiles.
- [ ] **R5 Gate Unlock**: `window.zoneEncounterProgress` tracks pack kills; gate unlocks automatically upon meeting threshold.
- [ ] **Regression Suite**: All 894 existing unit tests pass (`pytest tests/unit/ -q`).
- [ ] **Regression Features**: Waypoint safe radius (8.0 units, 2%/s heal, monster leash) and Pause Game (ESC / Settings modal) remain 100% operational.
