Agent skill

Landscape

by kevinpbuckley in kevinpbuckley/VibeUE

Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService).

MITAuto-check passedGame Development

Install Landscape

skills CLI
$ npx skills add kevinpbuckley/VibeUE --skill landscape -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install kevinpbuckley/VibeUE landscape --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .claude/skills && cp -r skills-src/Content/Skills/landscape .claude/skills/landscape && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
landscape
GitHub stars
730
Token cost
~7k tokens
SKILL.md length
2,770 words
Files
6 (incl. scripts)
Skills in repo
39
Repo updated
First seen
Licence
MIT

At a glance

Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService).

  • Works in 5 steps: Decide landscape config (e.g., 8×8… → Calculate resolution: (8 × 63 × 2) + 1 =… → Use terrain_data generate_heightmap with… → …
  • The user asks to create a landscape
  • SKILL.md covers Sub-docs available, Critical Rules, Related Skills and Sample scripts (run via…, plus 1 more section
  • Calls git

What it does

Landscape is an agent skill from kevinpbuckley/VibeUE. Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService). Use when the user asks to create a landscape, sculpt/raise/lower terrain, import or export a heightmap, paint terrain layers, or add procedural terrain features. For terrain materials load landscape-materials; for real-world heightmaps load terrain-data.

Its SKILL.md is about 7k tokens, which your agent loads only when the skill is triggered. The skill folder holds 6 other files, including scripts (for example `intelligent-sculpting.md`, `return-types.md` and `workflows-creation.md`).

It sits in Game Development. The repository describes itself as: Unreal Engine Vibe Coding tool. The licence is MIT.

When your agent uses it

  • The user asks to create a landscape
  • Sculpt/raise/lower terrain
  • Export a heightmap
  • Paint terrain layers

Example prompts

  • “/landscape”

Requirements

  • Python 3

Workflow steps

5 steps, taken from the first numbered list in SKILL.md.

  1. Decide landscape config (e.g., 8×8 components, 63 quads, 2 sections)
  2. Calculate resolution: (8 × 63 × 2) + 1 = 1009
  3. Use terrain_data generate_heightmap with resolution=1009 and map_size=N
  4. Create landscape with matching config
  5. Import heightmap — sizes match perfectly

What it can do on your machine

Read from SKILL.md and the folder at commit dc051be. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 1 file in scripts/, which the agent can run.

    Shell commands in SKILL.md call:

    • git

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use git, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Landscape loads about 7k tokens when it runs. Until then it costs about 112 tokens; SKILL.md has 2,770 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~112
When it runs · the whole SKILL.md, loaded when a task matches
~7k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from kevinpbuckley/VibeUE at commit dc051be, republished under its MIT licence (© kevinpbuckley). 2,770 words, ~7,000 tokens.

Download SKILL.mdSave it as .claude/skills/landscape/SKILL.md (or your agent's skills folder). This skill also uses 5 other files; get the full folder from GitHub.
name
landscape
description
Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService). Use when the user asks to create a landscape, sculpt/raise/lower terrain, import or export a heightmap, paint terrain layers, or add procedural terrain features. For terrain materials load landscape-materials; for real-world heightmaps load terrain-data.
display_name
Landscape Terrain
vibeue_classes
LandscapeService
unreal_classes
Landscape, LandscapeProxy, LandscapeInfo, LandscapeLayerInfoObject, LandscapeGrassType, GrassVariety
keywords
landscape, terrain, heightmap, sculpt, paint, layer, topography, grass, grass type, LGT, foliage, vegetation, mountain, valley, ridge, plateau, crater…

🧠 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.

Landscape Terrain Skill

Sub-docs available

This skill is split into the lean index (you are reading it) plus sibling sub-docs. Skills load via the engine's AgentSkillToolset (ListSkills / GetSkills) — request landscape/<section> through GetSkills, or read the sibling *.md file (it sits next to this SKILL.md on disk) directly:

Sub-docWhen to load
return-types.mdExact property names of LandscapeCreateResult, LandscapeInfo_Custom, LandscapeLayerInfo_Custom, LandscapeSplinePointInfo, LandscapeSplineSegmentInfo, LandscapeSplineMeshEntryInfo, plus the create_landscape signature.
workflows-creation.mdCreating landscapes, getting info, importing/exporting heightmaps, recreating splines, copying terrain/paint layers between landscapes, calculating side-by-side offsets, reading height data in a region.
workflows-editing.mdSculpting, direct region editing, procedural noise, painting layers, setting material, querying terrain, LandscapeGrassType creation/cloning, full landscape cloning, existence checks, heightmap sizing utilities.
intelligent-sculpting.mdv3 semantic features (mountain/valley/ridge/plateau/crater/terraces/erosion/blend), terrain analysis (slope/normal/flat areas), mesh projection, batch line traces, complete v3 workflow example.

Critical Rules

Valid Quad Sizes

QuadsPerSection must be one of: 7, 15, 31, 63, 127, 255 SectionsPerComponent must be 1 or 2

Common setup: QuadsPerSection=63, SectionsPerComponent=1, ComponentCount=8x8

⚠️ Performance: Landscape Creation Timeout

Creating landscapes with many components is SLOW. Python execution has a 30-second timeout.

Safe configurations (under 30s):

  • ComponentCount=4x4 (253x253 resolution) — safe for throwaway/test terrains
  • ComponentCount=8x8 (505x505 resolution) — can exceed 30s in a populated level (measured 67s in a level that already had another landscape); safe only in near-empty levels

Will TIMEOUT (avoid):

  • ComponentCount=16x16 or larger in a populated level
  • ComponentCount=36x36 or larger — too many components, takes minutes
  • ComponentCount=72x72 — definitely exceeds timeout

⚠️ A timeout does NOT cancel the operation. PYTHON_EXECUTION_TIMEOUT means the tool stopped waiting — the create keeps running on the game thread and usually completes. Never blindly retry: you'd stack a second landscape with the same label (then delete_landscape removes only the first match and landscape_exists stays true, which looks like a delete failure). After a timeout, check landscape_exists(label) first. create_landscape also refuses to create a landscape whose label already exists.

⚠️ Heightmap Import: Resolution MUST Exactly Match

import_heightmap() requires the heightmap file resolution to exactly match the landscape resolution. There is NO automatic scaling. A size mismatch will fail or produce flat/corrupt terrain.

If the target landscape label does not exist, import_heightmap() now auto-creates a matching landscape at world origin with default scale (100,100,100) before importing. For real-world terrain workflows, explicitly creating the landscape first is still recommended so you can control XY/Z scale.

Landscape resolution formula: Resolution = (ComponentCount × QuadsPerSection × SectionsPerComponent) + 1

Safe performant configs (common resolutions):

ComponentsQuadsSectionsResolutionkm at scale=100Notes
8×8631505×505~0.5 kmFast, good for prototypes
8×86321009×1009~1.0 kmGood balance of detail/speed
16×166311009×1009~1.0 kmSame resolution, more LOD components
8×812711017×1017~1.0 kmLarger quads, fewer components
16×166322017×2017~2.0 kmEpic recommended for medium landscapes
32×326312017×2017~2.0 kmSame resolution, more LOD components
8×812722033×2033~2.0 kmHigh detail, fewer components
16×1612712033×2033~2.0 kmHigh detail, more components
32×326324033×4033~4.0 kmLarge open world
32×3212728129×8129~8.1 kmMax single tile

⚠️ 1081×1081 is NOT a valid performant config! The only config producing 1081 is 36×36 components, 15 quads, 2 sections — which has 1296 components and will timeout. Never request or assume 1081.

Heightmap ↔ Landscape Sizing Utilities

Four Python utility functions help match heightmaps to landscapes:

FunctionPurpose
get_heightmap_dimensions(file_path)Read width/height/bitdepth of a PNG or RAW heightmap file
resize_heightmap(source, width, height, output, interpolation)Resample a 16-bit heightmap to new dimensions. Default interpolation="bicubic" (Catmull-Rom, smooth normals); pass "bilinear" only if you need the legacy behavior — bilinear upsampling of a low-res DEM produces blocky planar facets on flat plains
calculate_landscape_resolution(cx, cy, quads, sections)Compute resolution from landscape config parameters
find_landscape_config_for_resolution(width, height)Find the best landscape config that matches a given resolution

BEST approach — request the heightmap at the exact resolution you need:

  1. Decide landscape config (e.g., 8×8 components, 63 quads, 2 sections)
  2. Calculate resolution: (8 × 63 × 2) + 1 = 1009
  3. Use terrain_data generate_heightmap with resolution=1009 and map_size=N
    • map_size controls how many real-world km are captured (default 17.28)
    • Smaller map_size = more detail for a specific feature (e.g., 2-5 km for a single mountain)
    • Larger map_size = wider area with less per-pixel detail (e.g., 10-20 km for a region)
  4. Create landscape with matching config
  5. Import heightmap — sizes match perfectly
✅ Fallback Workflow: Resize After Download

If you already have a heightmap at a non-matching resolution:

  1. Check dimensions: get_heightmap_dimensions(file_path)
  2. Either:
    • Find a landscape config that matches: find_landscape_config_for_resolution(width, height)
    • Or resize the heightmap to match your landscape: resize_heightmap(source, target_w, target_h)
  3. Create landscape / import resized heightmap
World Partition landscapes (streaming proxies)

In a World Partition level, pass world_partition_grid_size (components per proxy grid cell, e.g. 2) to create_landscape to split the landscape into ALandscapeStreamingProxy actors — the same thing the editor's New Landscape tool does in a WP world:

python
result = unreal.LandscapeService.create_landscape(
    unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0), unreal.Vector(100, 100, 100),
    sections_per_component=1, quads_per_section=63,
    component_count_x=8, component_count_y=8,
    landscape_label="WPLandscape", world_partition_grid_size=2)
  • The default world_partition_grid_size=0 keeps the single monolithic ALandscape (old behavior).
  • The call fails with an error if the level is not World Partition — the engine's grid split is a silent no-op there, so VibeUE refuses instead of pretending.
  • import_heightmap, sculpting, painting, and region edits are proxy-transparent: they resolve the parent ALandscape and fan updates out to every proxy sharing its landscape GUID. Nothing else in your workflow changes.
🚨 Save semantics: the dirty list lies — audit with get_last_landscape_write() + git status

On a World Partition landscape the engine's dirtying mode (LandscapeSettings.LandscapeDirtyingMode, default In Landscape Mode And User Triggered Changes) does not mark an edit dirty when it is made outside Landscape mode — and every VibeUE/Python edit is outside Landscape mode. The engine tracks the touched packages in an internal modified-package list instead of the normal dirty flag. The practical consequences, all measured:

  • import_heightmap dirties the proxies but does not save them. You must save explicitly.
  • A brush / region / semantic op (flatten_at_location, set_height_in_region, sculpt/smooth/…) can write the proxy .uasset files to disk during the edit-layer resolve while the editor's dirty list shows nothing. One flatten_at_location left 49 proxy files modified on disk with an empty dirty list. So the dirty list is not a reliable audit of a WP landscape after a brush op.
  • git status is the only fully honest audit (the modified files live under Content/__ExternalActors__/...; restore an unwanted change with the editor closed via git checkout -- Content/__ExternalActors__/...).

Two tools make this auditable from Python:

python
import unreal
svc = unreal.LandscapeService

svc.flatten_at_location("Landscape", 0.0, 0.0, 0.0, 250.0)   # any height writer

# What did that write actually touch?
rep = svc.get_last_landscape_write()
print(rep.summary)                       # e.g. "49 proxies dirtied; 49 written to disk by the edit-layer flush (mtime changed)"
print(rep.num_proxies_dirtied, rep.num_written_to_disk, rep.b_any_written_to_disk)
for p in rep.proxies:
    print(p.package_name, "dirty:", p.b_dirty_after, "written:", p.b_written_to_disk, p.file_size_bytes)
  • get_last_landscape_write() reports, per proxy package the last write touched: b_dirty_before / b_dirty_after (the unreliable flag) and b_written_to_disk — an on-disk mtime comparison bracketing the edit-layer resolve, which is the signal you can trust. num_written_to_disk / b_any_written_to_disk summarise it.

    • Caveat: the mtime probe only sees writes that complete during the MCP call (around the synchronous ForceUpdateLayersContent resolve). If the engine ever defers the on-disk save to a later editor tick, b_written_to_disk reads false even though the file changes shortly after — so git status remains the final word.
  • save_landscape(label) is the deterministic, explicit save: it collects the parent ALandscape package plus every proxy sharing the landscape GUID and saves them all through UEditorLoadingAndSavingUtils::SavePackages(..., only_dirty=False) — i.e. regardless of the dirty flag. Use it after import_heightmap (which never saves), or any time you want a clean, audited flush.

python
res = svc.save_landscape("Landscape")
if res.b_success:
    print("saved", res.num_requested, "packages:", list(res.saved_packages))
else:
    print("save failed:", res.error_message)   # e.g. a read-only / source-controlled file
Blocky / faceted flat areas (plains)

Low-res source DEMs upsampled to landscape resolution show planar facets on flat terrain. Fixes, in order of preference:

  1. resize_heightmap(..., interpolation="bicubic") — now the default; re-run the resize+import if the landscape was built with an older (bilinear) resize.
  2. Increase blur_passes (20–40) on terrain_data generate_heightmap for plains-heavy regions.
  3. Material-level smoothing without touching heights: derive a world-space smoothed normal in the landscape material (see landscape-materials skill) when height data must stay pixel-exact.
Landscape Scale
  • Default scale (100, 100, 100) = 1 meter per unit
  • Larger Z scale = taller terrain range (e.g., Z=200 doubles height range)

⚠️ When importing real-world terrain from terrain_data, ALWAYS calculate Z scale:

z_scale = 20000 / height_scale

Do NOT hardcode Z scale. The height_scale from preview_elevation determines the correct Z scale.

Height Limits (uint16 Saturation)

The heightmap is uint16 (0–65535, midpoint 32768). Maximum world height depends on Z scale:

Z ScaleMax Height (world units)Min Height (world units)Total Range
100~25,599~-25,600~51,200
200~51,198~-51,200~102,400
50~12,800~-12,800~25,600

Formula: MaxHeight = (65535 - 32768) × (1/128) × ZScale

When sculpting pushes vertices to 0 or 65535, a saturation warning is logged. To increase range:

  • Increase landscape Z scale (halves vertical resolution)
  • Offset landscape Z location downward to use the full ±range
World Coordinates vs Landscape Coordinates
  • Most methods accept world coordinates (WorldX, WorldY)
  • get_height_in_region and set_height_in_region use landscape-local vertex indices
  • Heights in set_height_in_region are world-space Z values
  • Heights returned by get_height_in_region are world-space Z values
⚠️ GrassVariety Struct Properties Are Read-Only via Direct Assignment

GrassVariety (and its nested structs like PerPlatformFloat, FloatInterval, etc.) cannot be set via direct attribute assignment. You MUST use set_editor_property() for all properties.

python
# WRONG - raises "Property is read-only and cannot be set"
nv = unreal.GrassVariety()
nv.affect_distance_field_lighting = True
nv.grass_density.default = 50.0

# CORRECT - use set_editor_property and construct new struct instances
nv = unreal.GrassVariety()
nv.set_editor_property('affect_distance_field_lighting', True)
nv.set_editor_property('grass_density', unreal.PerPlatformFloat(default=50.0))
nv.set_editor_property('scale_x', unreal.FloatInterval(min=1.0, max=3.0))

This applies to ALL nested structs: PerPlatformFloat, PerPlatformInt, PerQualityLevelFloat, PerQualityLevelInt, FloatInterval, LightingChannels. Always construct a new struct instance with keyword args.

⚠️ Creating LandscapeGrassType Assets Requires AssetTools + Factory

LandscapeGrassType assets are NOT created via LandscapeService. Use AssetToolsHelpers with LandscapeGrassTypeFactory:

python
# WRONG - no such method exists
unreal.LandscapeService.create_grass_type("LGT_MyGrass", "/Game/Grass")

# CORRECT - use AssetTools + Factory
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
factory = unreal.LandscapeGrassTypeFactory()
new_asset = asset_tools.create_asset("LGT_MyGrass", "/Game/Grass", unreal.LandscapeGrassType, factory)
⚠️ Use LandscapeMaterialService for Landscape Materials - NOT MaterialService
  • CORRECT: unreal.LandscapeMaterialService.create_landscape_material("M_Terrain", "/Game/Mats")
  • WRONG: unreal.MaterialService.create_material("M_Terrain", "/Game/Mats") → causes TypeError: Nativize: Cannot nativize 'MaterialCreateResult' as 'String'

MaterialService is for generic materials. Landscape materials MUST use LandscapeMaterialService.

Layer Info Objects Required

Every paint layer needs a ULandscapeLayerInfoObject asset:

  1. Create with LandscapeMaterialService.create_layer_info_object()
  2. ALWAYS store and use .asset_path from the result — never guess paths
  3. Naming convention is LI_<LayerName> (e.g., LI_Grass, LI_Rock) — NOT Grass_LayerInfo
  4. Then add to landscape with LandscapeService.add_layer(landscape_label, info.asset_path)
Material Must Be Compiled First

After creating/changing a landscape material, compile it before assigning to the landscape.

Show full SKILL.md (1,127 more words)Show less
⚠️ Split Material Operations Into Separate Code Blocks

compile_material() can take minutes for complex landscape materials. NEVER combine material creation + compile + layer info creation in one code block — it WILL timeout.

Do this in separate steps:

  1. Block 1: Create material, add blend nodes, add layers, connect, save
  2. Block 2: compile_material() (this is slow)
  3. Block 3: Create layer info objects, assign to landscape

⚠️ Common Mistakes to Avoid
WRONGCORRECT
create_landscape(..., actor_label="X")create_landscape(..., landscape_label="X")
result.actor_pathresult.actor_label (no actor_path exists)
info.successCheck info.actor_label (no success field on info)
info.num_sectionsinfo.num_subsections
info.quads_per_sectioninfo.subsection_size_quads
MaterialService.create_material() for landscapesLandscapeMaterialService.create_landscape_material()
Guessing layer info path Grass_LayerInfoUse create_layer_info_object().asset_path → LI_Grass
segment.start_control_point_indexsegment.start_point_index (spline segment)
segment.end_control_point_indexsegment.end_point_index (spline segment)
layer.name on list_layers resultlayer.layer_name (LandscapeLayerInfo_Custom property)
LandscapeService.create_spline_control_point(...)LandscapeService.create_spline_point(...) (no "control" in name)
LandscapeMaterialService.add_material_layer(...)LandscapeMaterialService.add_layer_to_blend_node(...)
mat_path = create_landscape_material(...) as stringReturns LandscapeMaterialCreateResult — use result.asset_path
connect_spline_points(..., tangent_length=25.0) ignoring negative sourcePass tangent_length=seg.start_tangent_length, end_tangent_length=seg.end_tangent_length — both tangents are needed for exact shape
Only passing tangent_length to connect_spline_pointsMust also pass end_tangent_length=seg.end_tangent_length — end tangent is typically negative (UE convention). Omitting it defaults to -start_tangent which is usually correct for NEW splines, but when copying, always pass both explicitly
create_spline_point("L", location=vec)Parameter is world_location, NOT location: create_spline_point("L", world_location=vec)
modify_spline_point("L", 0, location=vec)Parameter is world_location, NOT location: modify_spline_point("L", 0, world_location=vec)
modify_spline_point(...) with wrong rotationUse auto_calc_rotation=False, rotation=pt.rotation to set exact rotation from get_spline_info
Setting rotation BEFORE connect_spline_pointsconnect_spline_points triggers auto-calc rotation — set rotations AFTER all segments are connected
pt.paint_layer_name on LandscapeSplinePointInfoProperty is pt.layer_name (NOT paint_layer_name) — paint_layer_name is the parameter name in create_spline_point
Reading weights right after painting and getting 0.0Weights are written to edit layer — reads use same path
spline_info.points on LandscapeSplineInfoProperty is spline_info.control_points (NOT points)
Not setting spline segment meshes after connectingUse set_spline_segment_meshes() to assign mesh entries (e.g. SM_River) — splines are green without meshes
Forgetting to set control point meshesUse set_spline_point_mesh() — control points can have their own static mesh
layer.asset_path on LandscapeLayerInfo_CustomProperty is layer.layer_info_path (NOT asset_path)
Guessing random offset like 110000 for side-by-sideCalculate: offset = (resolution - 1) * scale.x e.g. (1009-1)*100 = 100800
Using FoliageService.clear_all_foliage() thinking it only clears one landscapeclear_all_foliage() removes ALL foliage from the ENTIRE level — use remove_foliage_in_radius() or remove_all_foliage_of_type() to target specific areas
Manually scattering foliage to replicate a landscape that uses LandscapeGrassTypeIf foliage is procedural (from LandscapeGrassType on the material), just copy paint layers — foliage auto-generates from weights. Check if list_foliage_types() returns 0; if so, foliage is procedural.
Using terrain_data without resolution and getting 1081×1081Always pass resolution=N matching your landscape. Common values: 505, 1009, 1017, 2033
Assuming 1081×1081 heightmap will fit any landscape1081 requires 36×36 components which WILL timeout. Use resolution=1009 (8×8, 63q, 2s) instead
Importing a heightmap without checking dimensionsAlways call get_heightmap_dimensions() first, then resize_heightmap() if sizes don't match
Creating landscape then downloading heightmap (wrong order)Decide config → calculate resolution → download at that resolution → create landscape → import
info.scale_x on LandscapeInfo_Custominfo.scale.x — scale, location, rotation are Vector/Rotator structs, not flattened floats
Assuming a landscape is centered on its locationIt spans +X/+Y from its location (corner-anchored): bounds = location to location + (resolution-1)*scale. A landscape created at (0,0) with 505 res @ scale 100 covers (0,0)–(50400,50400); its center is (25200,25200)
create_layer_info_object("Grass", "/Game/X", "LI_Grass")Third param is a bool: create_layer_info_object(layer_name, destination_path, is_weight_blended=True) — passing a string asset name causes TypeError: Cannot nativize 'str' as 'bool'
AssetTools.create_asset(..., unreal.LandscapeLayerInfoObject, None) to make a layer infoYields a broken asset with LayerName=None, and layer_name is read-only from Python so it can't be fixed afterwards. Use LandscapeMaterialService.create_layer_info_object(layer_name, path) — or EditorAssetLibrary.duplicate_asset an existing LayerInfo, which keeps its baked LayerName
Retrying create_landscape after PYTHON_EXECUTION_TIMEOUTThe first call usually still completes in the background — check landscape_exists(label) before retrying (duplicate labels now return an error)
result.location on LandscapeCreateResultResult has ONLY success, actor_label, error_message — get location via get_landscape_info(label).location
result.resolution_x / .resolution_y on HeightmapImportResultThe field is result.resolution — a string like "1009x1009", not separate ints. Split it if you need numbers.
Treating get_height_at_location() as a floatIt returns a LandscapeHeightSample struct — use .height (float) and .valid (bool); float(sample) / f"{sample}" raise TypeError
Clearing a World-Partition landscape (parent + streaming proxies)delete_landscape(label) now removes the streaming proxies too by default (include_proxies=True): it unregisters each LandscapeStreamingProxy sharing the landscape's GUID from the LandscapeInfo, destroys the proxies, then the parent. Pass include_proxies=False for the parent actor only. Do NOT destroy_actor the proxies yourself — destroying a proxy while the LandscapeInfo still references it crashes the editor (access violation in the Landscape module). With the editor closed, deleting the proxy package files under __ExternalActors__ is the other safe route.
info.component_count_x / info.component_count_yinfo.num_components is the TOTAL count (e.g. 64 for 8×8) — there are no per-axis fields
info.rotation.x (Rotator)Rotator fields are .roll / .pitch / .yaw — there is no .x/.y/.z on Rotator (only Vector has those)

TaskSkills to Load
Sculpt terrain onlylandscape
Simple painted material (LayerBlend)landscape + landscape-materials
Production auto-material (material functions, RVT, instances)landscape + landscape-auto-material
Material instance / biome configurationlandscape-auto-material
  • landscape-materials: Simple landscape materials with LandscapeLayerBlend nodes. Good for prototyping with 2-5 layers.
  • landscape-auto-material: Production-quality auto-materials using material functions, Runtime Virtual Textures, and material instances (Real_Landscape paradigm). Good for shipping quality.

When you need to create or modify the material applied to a landscape, load the appropriate material skill through GetSkills (AgentSkillToolset):

  • For simple materials: load landscape-materials
  • For production auto-materials: load landscape-auto-material

NOTE: For landscape material, texture, and layer blending operations, use LandscapeMaterialService (load the landscape-materials or landscape-auto-material skill).

Sample scripts (run via execute_python_code)

  • scripts/sculpt_terrain.txt — create a landscape and sculpt procedural mountain/valley features.

Additional gotchas

  • On a World Partition landscape, verify every sculpt and every reader with a line_trace_single grid; the only safe write path is export_heightmap -> stamp in Python -> import_heightmap. Never synthesise a flat baseline — a flat-plus-pit import erases carved features.
  • Pixel mapping: origin is the proxy min corner, world = min + px * scale, worldZ = (H - 32768) * 100/128, no row flip; check against a known trace height before writing.
  • import_heightmap does not write proxies to disk — call save_landscape (or save_packages the Landscape and every proxy). Use get_last_landscape_write to see what changed, since the dirty list is unreliable right after landscape calls.
  • flatten_at_location and set_height_in_region read and write the same edit layer now, so a region write stays inside its brush on World Partition; apply_erosion was always safe.
  • load_level from Python leaves all WP cells unloaded for the rest of the session, and create_landscape run headless produces a broken landscape (zero heights, no collision).
  • Delete a landscape together with its streaming proxies via delete_landscape(include_proxies=True); destroying proxy actors by hand crashes the editor.
  • The edit-layer merge can re-surface old garbage after a later merge (a brush spawn, apply_splines_to_landscape); re-verify landmarks by trace after any landscape-touching op.
  • Spawning any WaterBody auto-spawns a WaterBrushManager (affects_landscape defaults True) that adds a "Water" edit layer and poisons the terrain; flip that CDO default first and delete any spawned manager. WaterZone.zone_extent is on the actor; the river/lake seam material is lake_transition_material; target_wave_mask_depth is the still-water knob.

© kevinpbuckley, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 5 other files (scripts) in Content/Skills/landscape of kevinpbuckley/VibeUE.

  • SKILL.md
  • intelligent-sculpting.md
  • return-types.md
  • scripts/sculpt_terrain.txt
  • workflows-creation.md
  • workflows-editing.md

Open the folder on GitHubat commit dc051be

Compare with similar skills

Landscape next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Landscape compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Landscape this skillkevinpbuckley/VibeUE730—~7kAutomated safety check: PassMIT
Image to Three.js Modelimg2threejs/img2threejs18k1 repos~8.2kAutomated safety check: PassApache-2.0
Web CloneJane-xiaoer/claude-skill-web-clone1k1 repos~2.7kAutomated safety check: PassMIT
Threejs Game Directormajidmanzarpour/threejs-game-skills2.5k—~2.2kAutomated safety check: PassMIT
Game Asset Generatorhtdt/godogen7.1k—~2.8kAutomated safety check: PassMIT
Threejs Gameplay Systemsvalkor-ai/loom1.2k1 repos~1.4kAutomated safety check: PassApache-2.0

Similar skills

  • Image to Three.js Model

    img2threejs/img2threejs

    Rebuilds the object in a reference image as a procedural, animation-ready Three.js model written entirely in code, using staged sculpting with quality checks.

    18k GitHub starsUsed in 1 repo~8.2k tokens
    Game DevelopmentAuto-check passed
  • Web Clone

    Jane-xiaoer/claude-skill-web-clone

    网站复刻 / 克隆方法论。USE WHEN 用户说 复刻网站、克隆网站、clone website、抄个站、仿站、 照着这个站做一个、reproduce site、还原某个网页效果、把这个站搬下来改成我的、 复刻某个交互/WebGL/Canvas/Three.js 效果。提供「先拿真源码 → 判路径 → 逆向拆解 → 搭工程 → 替换内容」的可移植决策树,覆盖静态站 /…

    1k GitHub starsUsed in 1 repo~2.7k tokens
    Game DevelopmentAuto-check passed
  • Threejs Game Director

    majidmanzarpour/threejs-game-skills

    Entrypoint for building, upgrading, and finishing Three.js browser games.

    2.5k GitHub stars~2.2k tokensUpdated 12 days ago
    Game DevelopmentAuto-check passed
  • Generates game art from text prompts: PNG images, GLB 3D models, rigged characters, animations and sprites, with background removal.

    7.1k GitHub stars~2.8k tokensUpdated 8 days ago
    Game DevelopmentAuto-check passed
  • Build and iterate playable Three.js game systems: starter scaffold, architecture, design briefs, core loops, level and encounter design, entities, input, camera, collision and physics, scoring…

    1.2k GitHub starsUsed in 1 repo~1.4k tokens
    Game DevelopmentAuto-check passed
  • Uloop Execute Dynamic Code

    CyberAgentGameEntertainment/NovaShader

    Execute C with Unity APIs when existing uloop tools cannot inspect or edit enough.

    1.6k GitHub starsUsed in 1 repo~1.9k tokens
    Game DevelopmentAuto-check passed

More from kevinpbuckley/VibeUE

All 39 skills in this repo
  • Unreal Animation Bone Editor

    kevinpbuckley/VibeUE

    Previews, validates and bakes bone-rotation edits on Unreal Engine AnimSequences, enforcing a coordinate space and a constraint-checked preview-validate-bake loop.

    730 GitHub stars~1.3k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Animation Montage

    kevinpbuckley/VibeUE

    Creates and edits Unreal Engine Animation Montages through AnimMontageService: sections, slots, segments, branching points, notifies and blend settings.

    730 GitHub stars~3.6k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal AnimSequence Editor

    kevinpbuckley/VibeUE

    Creates and edits AnimSequence keyframes and bone tracks in Unreal Engine through AnimSequenceService, following an inspect, preview, validate and bake workflow with correct bone-space handling.

    730 GitHub stars~3.2k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Asset Import and Export

    kevinpbuckley/VibeUE

    Imports files into Unreal Engine, exports textures, reads the Content Browser selection and checks open assets via VibeUE, plus mesh reimport material fixes.

    730 GitHub stars~6.5k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Engine Settings Control

    kevinpbuckley/VibeUE

    Changes Unreal Engine console variables, scalability levels and raw INI values through VibeUE's Python API, now that registered settings moved to the engine's own toolset.

    730 GitHub stars~1.3k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Enhanced Input Setup

    kevinpbuckley/VibeUE

    Creates and wires Unreal Engine Enhanced Input assets through VibeUE's InputService: Input Actions, Mapping Contexts, key bindings, triggers and modifiers.

    730 GitHub stars~2.4k tokensUpdated 2 days ago
    Auto-check passed

Questions about Landscape

What does Landscape do?

Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService). Landscape is an agent skill from kevinpbuckley/VibeUE. Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService).

When should I use Landscape?

Landscape fits situations like: the user asks to create a landscape; sculpt/raise/lower terrain; export a heightmap; paint terrain layers.

How do I install Landscape in Claude Code?

Run `npx skills add kevinpbuckley/VibeUE --skill landscape -a claude-code`. Or copy the skill folder (Content/Skills/landscape in kevinpbuckley/VibeUE) into .claude/skills/landscape in your project. Claude Code loads it when a task matches its description.

How do I install Landscape in Codex?

Run `npx skills add kevinpbuckley/VibeUE --skill landscape -a codex`. Or copy the skill folder (Content/Skills/landscape in kevinpbuckley/VibeUE) into .agents/skills/landscape in your project. Codex loads it when a task matches its description.

Can I use Landscape in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add kevinpbuckley/VibeUE --skill landscape -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/landscape, .gemini/skills/landscape, .github/skills/landscape and .opencode/skills/landscape in your project.

What does Landscape need to run?

Going by SKILL.md and its folder, Landscape needs the command-line tools its instructions call (git). Our summary lists: Python 3.

Does Landscape access the network?

SKILL.md contains no URLs. Its commands use git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Landscape safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does Landscape use?

Landscape is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Landscape use?

About 7k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Landscape?

Skills that share tags, products or a category with Landscape: Image to Three.js Model (img2threejs/img2threejs, 18k stars), Web Clone (Jane-xiaoer/claude-skill-web-clone, 1k stars), Threejs Game Director (majidmanzarpour/threejs-game-skills, 2.5k stars) and Game Asset Generator (htdt/godogen, 7.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Landscape?

kevinpbuckley (a GitHub user) maintains it in kevinpbuckley/VibeUE, which has 730 GitHub stars. The repository holds 39 skills in this directory. The repository was last updated on October 7, 2026.

Source: kevinpbuckley/VibeUE on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.