---
name: terrain-data
display_name: Real-World Terrain
description: Generate heightmaps, map reference images, and water feature splines (rivers, lakes, oceans) from real-world geographic data (terrain_data tool). Use when the user asks to build terrain from a real location/coordinates, download a real-world heightmap, or add rivers/lakes/oceans from map data. Downloads Mapbox tiles server-side and produces UE5-compatible heightmaps + landscape splines.
keywords:
  - terrain
  - heightmap
  - real world
  - real-world
  - elevation
  - topography
  - satellite
  - map image
  - cities skylines
  - mapbox
  - import heightmap
  - landscape import
  - mount fuji
  - san francisco
  - real terrain
  - geographic
  - coordinates
  - latitude
  - longitude
  - topo
  - water
  - river
  - lake
  - ocean
  - waterway
  - stream
  - canal
  - water features
  - spline
  - river spline
  - lake spline
---

> 🧠 **Brains complement:** IF an `unreal-engine-skills-manager` tool (external MCP) exists in this session, call it with `{action: "load", skill: "landscape-and-foliage"}` for UE domain knowledge on this topic — correct APIs, architecture, best practices — and treat it as the rubric for any review / "best practices" question. If no such tool is available (e.g. running under Claude Code or Codex without that MCP), skip this line entirely and proceed with this skill alone — do NOT attempt the call.

# Real-World Terrain Skill

Generates heightmaps and water feature splines from real geographic coordinates via the `terrain_data`
MCP tool. The tool downloads Mapbox tiles server-side and writes UE5-compatible heightmaps + landscape
splines into the project's `Saved/Terrain/` folder — no API key or chat configuration is required.

## Workflow

### Heightmap (always run first)

1. `preview_elevation` — fetch elevation stats + suggested settings. Returns `suggestedZScale`, `suggestedXYScales` (resolution → XY scale), height range.
2. `generate_heightmap` — generate the 16-bit PNG using suggested settings
3. *(optional)* `get_map_image` — satellite/topo reference image for the same area
4. **`attach_image`** — attach the satellite image so you can **see** the terrain colors and features for material/painting decisions
5. Import into UE5 using the `landscape` skill

**⚠️ CRITICAL — Landscape Scale:** When creating the landscape, use `suggestedXYScales[resolution]` from step 1 as the X and Y scale. Do NOT use the default value of 100. Use `suggestedZScale` for the Z scale. Example: `scale=unreal.Vector(suggestedXYScales["1009"], suggestedXYScales["1009"], suggestedZScale)`

### Water features (after heightmap is imported)

6. `get_water_features` — fetch rivers, lakes, and oceans for the same lng/lat/map_size. **Saves full JSON to `Saved/Terrain/` and returns a compact summary with the file path.**
7. Read the saved JSON file: `json_str = open(file_path).read()`
8. Water bodies use `ue5_rings`, waterways use `ue5_points`. Both are origin-centered.

---

## Test Prompts

```
Create a landscape from the terrain around Mount Fuji
```
```
Generate a heightmap for San Francisco at coordinates -122.4194, 37.7749
```
```
Build a real-world landscape from the Swiss Alps near Zermatt (lng=7.7480, lat=46.0207)
```
```
Get the terrain around the Grand Canyon and make a landscape
```
```
Generate heightmap for Tokyo at 139.6917, 35.6895 with a satellite reference image
```

---

## Step-by-Step Example (Mount Fuji)

### Step 1 — Preview elevation

```
terrain_data(action="preview_elevation", lng=138.7274, lat=35.3606)
```

Response:
```json
{
  "min_height": 340.0,
  "max_height": 3776.0,
  "height_range": 3436.0,
  "suggested_base_level": 340,
  "suggested_height_scale": 27,
  "suggestedZScale": 741,
  "suggestedXYScales": { "505": 3429, "1009": 1714, "2017": 857, "4033": 429, "8129": 213 },
  "tile_zoom": 13,
  "tile_count": 9
}
```

Use `suggestedXYScales["1009"]` (or whichever resolution you chose) as the X and Y scale when calling `create_landscape`. Use `suggestedZScale` for the Z scale.

### Step 2 — Generate heightmap using suggested values

```
terrain_data(
  action="generate_heightmap",
  lng=138.7274, lat=35.3606,
  base_level=340,
  height_scale=27,
  format="png"
)
```

Response:
```json
{
  "success": true,
  "file": "C:/Project/Saved/Terrain/heightmap_35.3606_138.7274.png",
  "min_height_m": 340.0,
  "max_height_m": 3776.0,
  "dimensions": "1081x1081"
}
```

### Step 3 — Get satellite reference image

```
terrain_data(
  action="get_map_image",
  lng=138.7274, lat=35.3606,
  style="satellite-v9"
)
```

Response:
```json
{
  "success": true,
  "file": "E:/Project/Saved/Terrain/map_satellite_v9_35.3606_138.7274.png",
  "style": "satellite-v9",
  "size_bytes": 1981147
}
```

### Step 3b — Attach satellite image for vision analysis

**⚠️ CRITICAL: After downloading a satellite image, ALWAYS attach it so you can see the terrain colors and features.** This lets you make informed decisions about material layers and painting.

```
attach_image(file_path="E:/Project/Saved/Terrain/map_satellite_v9_35.3606_138.7274.png")
```

`attach_image` is a **tool call** (like `terrain_data`), NOT a Python function. Do NOT put it inside `execute_python_code`. Call it directly as a tool. If no `attach_image` tool is available in your session, skip this step and proceed using the saved file path returned by `get_map_image`.

After attaching, you will see the satellite image in your next response. Use it to:
- Identify terrain features (rock, grassland, water, sand, forest, urban)
- Choose appropriate material layer names and colors
- Design accurate procedural painting rules (height/slope thresholds)
- Match real-world color distribution to layer weights

### Step 4 — Import into UE5

Use the `landscape` skill to import the heightmap file:
- Component setup: `QuadsPerSection=63, SectionsPerComponent=2, ComponentCount=8x8` → 1009×1009 resolution
- **X and Y scale**: Use `suggestedXYScales["1009"]` from preview (e.g. 1714 for a 17.28km map). **Do NOT use the default 100** — that makes the landscape ~17x too small!
- **Z scale**: Use `suggestedZScale` from preview (e.g. 741 for Mount Fuji)
- File: the `.png` path returned in step 2

Example:
```python
result = unreal.LandscapeService.create_landscape(
    location=unreal.Vector(0, 0, 0),
    rotation=unreal.Rotator(0, 0, 0),
    scale=unreal.Vector(1714, 1714, 741),  # XY from suggestedXYScales["1009"], Z from suggestedZScale
    sections_per_component=2,
    quads_per_section=63,
    component_count_x=8,
    component_count_y=8,
    landscape_label="MyLandscape"
)
```

---

## Parameters Reference

### generate_heightmap

| Parameter | Default | Notes |
|-----------|---------|-------|
| `lng` / `lat` | required | Decimal degrees. Positive = East/North |
| `format` | `png` | `png` = 16-bit grayscale, `raw` = binary, `zip` = PNG + info |
| `map_size` | `17.28` | km — Cities: Skylines standard |
| `base_level` | `0` | Use `suggested_base_level` from preview |
| `height_scale` | `100` | Use `suggested_height_scale` from preview |
| `water_depth` | `40` | Cities: Skylines water units |
| `gravity_center` | `0` | 0=off, 2=N, 4=E, 6=S, 8=W (tilts terrain for water flow) |
| `level_correction` | `0` | 0=none, 2=flatten coastlines, 3=aggressive |
| `blur_passes` | `10` | **Adjust per terrain character!** 5–10=rugged, 15–25=hills, 25–40=smooth/flat. See Terrain Character Guide |
| `plains_height` | `140` | Meters — threshold between plains and mountains |
| `save_path` | auto | Saves to `<ProjectDir>/Saved/Terrain/` by default |

### get_map_image styles

| Style | Description |
|-------|-------------|
| `satellite-v9` | Aerial imagery (best for landscape texturing) |
| `outdoors-v11` | Topo with trails and contours |
| `streets-v11` | Street map |
| `light-v10` / `dark-v10` | Minimal |

---

## UE5 Landscape Import Settings

### Valid Resolutions — Always Pass `resolution=N`

**⚠️ Never use the default 1081×1081 for UE landscapes** — it requires 36×36 components and will timeout. Always pass `resolution=` matching your landscape config.

| resolution= | Components | Quads | Sections | km at UE scale=100 |
|-------------|-----------|-------|----------|--------------------|
| 505         | 8×8       | 63    | 1        | ~0.5 km            |
| 1009        | 8×8       | 63    | 2        | ~1.0 km            |
| 1009        | 16×16     | 63    | 1        | ~1.0 km            |
| 2017        | 16×16     | 63    | 2        | ~2.0 km            |
| 4033        | 32×32     | 63    | 2        | ~4.0 km            |
| 8129        | 32×32     | 127   | 2        | ~8.1 km            |

> "km at scale=100" is the UE world size when landscape XY scale = 100 cm/quad (default). Adjust scale to match real geography (see below).

### Matching Real-World Scale

`terrain_data`'s `map_size` (default: 17.28 km) controls the geographic area captured. To make the UE landscape match actual geography:

```
UE XY Scale (cm/quad) = (map_size_km × 100,000) / (resolution − 1)
```

Example: `map_size=20`, `resolution=2017` → `(20 × 100,000) / 2016 ≈ 992 cm/quad (~9.9 m/quad)`

Set this as the landscape's X and Y scale when creating it.

### Format and Z Scale

- **Format**: 16-bit grayscale PNG
- **Z Scale**: `20000 / height_scale` cm — e.g., `height_scale=27` → Z scale ≈ 741

**⚠️ ALWAYS calculate Z Scale from height_scale. Do NOT guess.** Use this formula when creating the landscape:

```
z_scale = 20000 / height_scale
```

Derivation: pixel encoding is `pixel = elevation_m × heightScale × 64 / 100` and UE interprets `height_cm = pixel × z_scale / 128`. Setting `height_cm = elevation_m × 100` (meters→cm) gives `z_scale = 20000 / heightScale`.

| height_scale | Z Scale | Terrain Type Example |
|-------------|---------|---------------------|
| 27          | 741     | Tall mountains (Mt. Fuji, Alps) |
| 50          | 400     | Moderate mountains (Appalachians) |
| 100         | 200     | Hills, canyons (Grand Canyon) |
| 150         | 133     | Low hills, mesas |
| 200         | 100     | Gentle rolling terrain |
| 250         | 80      | Very flat terrain, gentle domes |

For Cities: Skylines, export as PNG and import via the standard heightmap importer (1081 default is fine there).

---

## Terrain Character Guide

**⚠️ CRITICAL: The `suggested_height_scale` from `preview_elevation` maximizes detail, but also amplifies noise. You MUST adjust `blur_passes` based on the terrain character.**

### Identifying Terrain Character

After `preview_elevation`, look at the `height_range` and think about what the terrain actually looks like:

| height_range | suggested_height_scale | Terrain Character | blur_passes |
|-------------|----------------------|-------------------|-------------|
| > 2000m     | Low (< 50)           | **Rugged mountains** — keep default blur | 5–10 |
| 500–2000m   | Moderate (50–100)    | **Mixed terrain** — moderate smoothing | 10–15 |
| 100–500m    | High (100–200)       | **Hills/mesas** — needs more smoothing | 15–25 |
| < 100m      | Very high (200–250)  | **Flat/gentle** — needs heavy smoothing | 25–40 |

### Smooth vs Rugged Terrain

**Smooth terrain** (granite domes, rolling hills, plains, gentle slopes):
- Use higher `blur_passes` (20–40) to remove data noise
- The high `height_scale` amplifies every pixel of noise — smoothing counteracts this
- Examples: Enchanted Rock, Uluru, sand dunes, prairies

**Rugged terrain** (jagged peaks, canyons, volcanic craters):
- Use lower `blur_passes` (5–10) to preserve detail
- Lower `height_scale` means less noise amplification, so less smoothing needed
- Examples: Grand Canyon, Matterhorn, Iceland lava fields

### Common Mistake: Jagged Smooth Terrain

If you get a terrain that looks jagged/spiky when the real place is smooth:
1. The `height_scale` was too high without enough `blur_passes`
2. Fix: Re-generate with `blur_passes=30` or higher
3. Also check: Z scale should be `20000 / height_scale`, NOT an arbitrary value like 200

---

## Water Features Reference

### get_water_features

Fetches waterways and water bodies for a map area using the same Mapbox Vector Tile source as the heightmap. Use the **exact same `lng`, `lat`, and `map_size`** as your heightmap call.

```
terrain_data(action="get_water_features", lng=-105.0, lat=39.7, map_size=17.28)
```

Response includes:
- `file` — path to the saved JSON file (e.g. `Saved/Terrain/water_features_42.9720_-71.3480_10km.json`)
- `num_waterways` / `num_water_bodies` — counts
- `waterway_class_breakdown` — object with class counts, e.g. `{"stream": 191, "river": 26, "ditch": 12}`
- `waterways[]` — summary of each waterway: `name`, `class`, `estimated_width_m`, `num_points`
- `water_bodies[]` — summary of each water body: `name`, `class`, `num_ring_points`
- `message` — instructions on how to use the saved file, coordinate system info
- `ue5_coordinate_note` — critical info about coordinate offset for water planes

The **saved JSON file** contains the full data with:
- `waterways[]` — rivers, streams, canals. Each has `name`, `class`, `estimated_width_m`, `points` (lng/lat array), **`ue5_points`** (array of `{x, y, z}` objects in UE5 coords)
- `water_bodies[]` — lakes, ponds, oceans. Each has `name`, `class`, **`rings`** (lng/lat array of arrays), **`ue5_rings`** (array of arrays of `{x, y, z}` objects in UE5 coords)

**⚠️ CRITICAL FIELD NAMES:**
- Waterway positions: `ue5_points` (NOT `points` — those are lng/lat)
- Water body polygons: `ue5_rings` (NOT `polygon` or `ring`)
- Waterway type: `class` (NOT `waterway_class` or `type`)

### Coordinate system

`ue5_points` and `ue5_rings` are **landscape-center-relative** (origin-centered):
- Map geographic center = **(0, 0, Z)** in UE5 space
- **+X = East, +Y = North**, 1 meter = 100 UU

---

## Troubleshooting

| Issue | Fix |
|-------|-----|
| `429` rate limit | The Mapbox tile source is rate-limited — wait and retry, or reduce request frequency |
| Flat heightmap | `height_range` < 50m — use `height_scale: 250` for detail |
| Clipped mountains | Lower `height_scale` or increase `base_level` |
| Jagged/spiky terrain | Increase `blur_passes` (20–40 for smooth terrain) and check Z scale formula |
| Terrain doesn't match real place | Check terrain character guide — smooth places need high blur_passes |
| Timeout | Large map sizes at high zoom may hit Vercel's 10s limit — try smaller `map_size` |
| No waterways returned | Area may have no mapped water features (desert, urban grid) — check satellite image |
| Landscape too small | You used XY scale=100 (default) instead of the correct value from `suggestedXYScales`. Recreate with the correct scale |
| Black screenshots after camera move | Camera inside terrain. Use `ActorService.get_actor_view_camera()` to frame from TOP view instead of guessing Z height |
| Water features timeout | PBF tile fetch uses 45s timeout — large map_size at high zoom may still be slow |
| Field name errors | Use `ue5_points` (not `points`), `ue5_rings` (not `polygon`), `class` (not `waterway_class`) |

