--- 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 `/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`) |