Version 2.5.0-dev - A professional Python tool for generating custom heightmaps for Cities Skylines 2. Features hybrid zoned terrain generation with hydraulic erosion achieving 55-65% buildable terrain, evidence-based geological simulation, and advanced user controls.
- 16-bit Grayscale PNG Export: CS2-compliant heightmap format
- Dual Interfaces: CLI for automation, GUI for visual editing
- 7 Terrain Presets: Flat, Hills, Mountains, Islands, Canyons, Highlands, Mesas
- Procedural Generation: Perlin noise, Simplex noise, ridged multifractal, islands, canyons, mesas
- Auto-Export to CS2: Automatically detects and exports to CS2 directory (Windows/macOS/Linux)
- ⚡ Ultra-Fast Generation: 4096x4096 terrain in <1 second (60-140x faster than v2.0)
- Vectorized Processing: C++/Cython-accelerated noise generation
- GUI Interface: Tkinter-based visual editor with instant terrain updates (v2.1.1: preview updates automatically)
- Elevation Legend: Dedicated panel showing quantitative height scale - fully visible labels (v2.1.1: layout fixed)
- State Management: Full undo/redo support for all operations
- Water Features: Fully functional river networks (D8 algorithm), lake generation (watershed), coastal features (v2.1.1: now working)
- Terrain Analysis: Comprehensive statistics including slope/aspect/classification (v2.1.1: now accessible via GUI)
- CS2 Export: Direct export to Cities Skylines 2 heightmaps directory (v2.1.1: now working)
- Preview Generation: Hillshade rendering, multiple colormaps, thumbnail export
- Preset Management: Save/load/share custom terrain configurations
- Worldmap Support: Extended terrain beyond playable area
- Full pipeline (Sessions 2-8): 3-5 minutes (4096×4096 with 100k erosion particles)
- Zone generation: < 1s
- Weighted terrain: 3-4s
- Ridge enhancement: 8-10s
- Hydraulic erosion: 2-4 min (Numba JIT optimized)
- River analysis: ~20s
- Detail and verification: ~15s
- Quick test mode: ~1s (512×512, 5k particles for validation)
- Throughput: ~19 million pixels/second (noise generation)
- Scaling: Near-perfect linear scaling with resolution
- Setup verification: Built-in dependency checker ensures optimal performance
- GUI responsiveness: Background threading prevents UI freezing during generation
- ⚡ Hydraulic Erosion: Physically-accurate pipe model with Numba JIT optimization
- Creates realistic dendritic drainage networks (like real mountains)
- Performance: 1.47s for 50 iterations at 1024×1024 (5-8× faster with Numba)
- Performance: 40-45s for 100 iterations at 4096×4096 (default quality)
- Graceful fallback: Works on all systems (pure NumPy fallback if Numba unavailable)
- Recursive Domain Warping: Eliminates grid patterns, adds geological authenticity
- Ridge Continuity: Connects mountain ridges while preserving valley detail
-
Buildability Zone Generation: Continuous [0,1] buildability potential maps using low-frequency Perlin noise
- Large-scale zones (6500m wavelength) define WHERE buildability should exist
- Continuous values (not binary) for smooth transitions
- Geological structure-based zone placement
-
Zone-Weighted Terrain: Amplitude modulation based on buildability potential
- Buildable zones (P=1.0): 30% amplitude → gentle terrain
- Scenic zones (P=0.0): 100% amplitude → full detail
- SAME noise octaves everywhere (avoids frequency discontinuities)
- Smooth continuous modulation prevents pincushion problem
-
Ridge Enhancement: Sharp ridgelines in scenic zones only
- Absolute value transform creates V-shaped valleys
- Smoothstep blending for C¹ continuity
- Zone-restricted (P < 0.4 only, preserves buildable flatness)
-
Particle-Based Hydraulic Erosion: 100k-200k particles with zone modulation
- Strong erosion in buildable zones → flat valleys through deposition
- Gentle erosion in scenic zones → preserve mountain character
- Numba JIT optimization (5-8× speedup)
- Emergent buildability through sediment physics
-
River Network Analysis: D8 flow accumulation and river path detection
- Automatic drainage network identification
- Hydraulic geometry-based river widths
- Natural dam site identification
-
Detail Addition & Constraint Verification: Achieves 55-65% buildable target
- Conditional micro-scale detail (steep areas only)
- Conservative auto-adjustment if below target
- Final buildability verification and recommendations
Performance: Complete pipeline runs in 3-5 minutes at 4096×4096 with professional-quality results. Numba JIT compilation provides 5-8× speedup for particle erosion with zero code complexity.
- Python 3.8 or higher
- Cities Skylines 2 (for auto-export feature)
Windows:
setup_env.bat
macOS/Linux:
chmod +x setup_env.sh
./setup_env.sh
This will:
- Create a virtual environment
- Install all dependencies (including critical performance library)
- Set everything up for you
After setup, verify everything works:
python tests/verify_setup.py
This checks that all critical dependencies are installed and performance is optimal.
If you prefer manual installation:
- Create a virtual environment:
# Windows
python -m venv venv
venv\Scripts\activate
# macOS/Linux
python3 -m venv venv
source venv/bin/activate
- Install dependencies:
pip install -r requirements.txt
Before using the tool, activate the virtual environment:
Windows:
venv\Scripts\activate
macOS/Linux:
source venv/bin/activate
The CS2 Heightmap Generator offers two interfaces:
Launch the visual interface:
python gui_main.py
GUI Features:
- Live Preview: Real-time hillshade visualization with terrain colormap (updates automatically!)
- Elevation Legend: Dedicated panel showing quantitative heights (0m-4.1km) with fully visible labels
- Parameter Controls: 7 terrain presets + adjustable sliders (Scale, Octaves, Persistence, Lacunarity)
- Fixed Resolution: 4096x4096 (CS2 requirement - not optional)
- Zoom & Pan: Mouse wheel zoom (0.25x-4x), drag to pan
- Water Features: Fully functional rivers (D8), lakes (watershed), coastal features (beaches/cliffs)
- Terrain Analysis: View comprehensive statistics (height distribution, slope analysis, terrain classification)
- Menu Bar: File operations (New, Open, Save, Export to CS2), Edit (Undo/Redo), Tools (Water Features, Analysis)
- Keyboard Shortcuts: Ctrl+N (New), Ctrl+S (Save), Ctrl+Z (Undo), Ctrl+Y (Redo)
Quick Workflow:
- Launch GUI:
python gui_main.py
(opens instantly with blank preview) - Click a preset (Mountains, Islands, etc.) to generate terrain (<1 second - instant!)
- Add water features via Tools menu:
- Add Rivers (specify count)
- Add Lakes (specify count)
- Add Coastal Features (specify water level)
- View Analysis via Tools → Terrain Analysis (comprehensive statistics)
- Adjust parameters using sliders (changes update after 500ms)
- Use File → Export to CS2 to copy directly to Cities Skylines 2 (auto-detects directory)
Note: Preview now updates automatically after generation - no clicking required!
Generate heightmaps via command line:
python generate_map.py [preset] [map_name]
Examples:
# Generate with preset
python generate_map.py mountains "Alpine Valley"
# List available presets
python generate_map.py --list
# Show system info
python generate_map.py --info
from src.heightmap_generator import HeightmapGenerator
from src.noise_generator import create_preset_terrain
from src.cs2_exporter import quick_export
# Generate terrain
terrain = create_preset_terrain('mountains', resolution=4096, seed=42)
# Create heightmap
heightmap = HeightmapGenerator(resolution=4096, height_scale=4096)
heightmap.set_height_data(terrain)
# Export locally
heightmap.export_png('output/my_map.png')
# Export to CS2
quick_export('output/my_map.png', 'My Mountain Map')
Check the examples/
folder for complete working examples:
cd examples
python 01_basic_usage.py # Basic terrain generation
python 02_preset_terrains.py # Different terrain presets
python 03_with_worldmap.py # Heightmap + worldmap
python 04_custom_terrain.py # Custom terrain design
CRITICAL: Based on the official Cities Skylines 2 wiki, these specifications are MANDATORY:
- Resolution: 4096x4096 pixels (REQUIRED - not optional)
- Format: 16-bit grayscale PNG or TIFF (REQUIRED)
- Height Scale: Default 4096 meters (customizable in game)
- Value Range: 0 (lowest) to 65535 (highest)
- Location:
C:/Users/[username]/AppData/LocalLow/Colossal Order/Cities Skylines II/Heightmaps/
WARNING: Heightmaps with any other resolution will NOT work in Cities Skylines 2. The game requires exactly 4096x4096 pixels.
- Resolution: 4096x4096 pixels (same as heightmap)
- Center Area: 1024x1024 pixels matches the playable heightmap
- Surrounding: Shows unplayable terrain for visual context
- Format: Same as heightmap (16-bit grayscale PNG)
The tool includes several pre-configured terrain types:
Preset | Description | Best For |
---|---|---|
flat |
Completely flat terrain | Testing, custom design base |
rolling_hills |
Gentle rolling hills | Suburban development |
mountains |
Dramatic mountain ranges | Alpine cities, challenges |
islands |
Island archipelago | Coastal cities, bridges |
canyon |
Deep valleys and canyons | Desert cities, unique layouts |
highlands |
High plateau terrain | Mountain plateaus |
mesas |
Flat-topped formations | Southwest-style terrain |
from src.heightmap_generator import HeightmapGenerator
from src.noise_generator import NoiseGenerator
# Initialize
heightmap = HeightmapGenerator(resolution=4096, height_scale=5000)
# Create base with gradient
heightmap.create_gradient(start_height=0.3, end_height=0.7, direction='vertical')
# Add a mountain
heightmap.add_circle(center_x=0.5, center_y=0.5, radius=0.2, height=0.3, blend=True)
# Add detail with noise
noise_gen = NoiseGenerator(seed=123)
detail = noise_gen.generate_perlin(resolution=4096, scale=100.0, octaves=4)
combined = heightmap.get_height_data() * 0.8 + detail * 0.2
heightmap.set_height_data(combined)
# Smooth and export
heightmap.smooth(iterations=2)
heightmap.export_png('output/custom.png')
from src.worldmap_generator import create_worldmap_preset
# Generate playable heightmap first
playable_data = create_preset_terrain('mountains', resolution=4096, seed=999)
heightmap = HeightmapGenerator(resolution=4096)
heightmap.set_height_data(playable_data)
heightmap.export_png('output/map.png')
# Create worldmap with ocean surrounding
noise_gen = NoiseGenerator(seed=999)
worldmap = create_worldmap_preset(
preset='ocean',
playable_heightmap=heightmap.get_height_data(),
noise_generator=noise_gen
)
worldmap.export_png('output/map_worldmap.png')
Understanding noise parameters helps create specific terrain types:
noise_gen = NoiseGenerator(seed=42)
terrain = noise_gen.generate_perlin(
resolution=4096,
scale=200.0, # Larger = smoother terrain (50-500)
octaves=6, # More = more detail (1-8)
persistence=0.5, # Lower = smoother (0.3-0.7)
lacunarity=2.0 # Usually keep at 2.0
)
Parameter Guide:
- Scale: Controls wavelength of features
- 50-100: Rough, detailed terrain
- 150-250: Realistic rolling terrain
- 300+: Very smooth, large features
- Octaves: Number of detail layers
- 1-3: Simple, smooth
- 4-6: Realistic detail
- 7-8: Very detailed (slower)
- Persistence: Detail strength falloff
- 0.3-0.4: Smooth hills
- 0.5: Balanced realism
- 0.6-0.7: Rough mountains
CS2_Map/
├── src/
│ ├── features/ # Water features, erosion
│ │ ├── hydraulic_erosion.py # Pipe model erosion (Numba JIT)
│ │ ├── river_generator.py # D8 flow, river networks
│ │ ├── water_body_generator.py # Lake detection
│ │ ├── coastal_generator.py # Beach/cliff generation
│ │ ├── terrain_editor.py # Manual editing
│ │ └── performance_utils.py # Downsampling utilities
│ ├── gui/ # GUI components
│ │ ├── heightmap_gui.py # Main Tkinter application
│ │ ├── parameter_panel.py # Control sliders
│ │ ├── preview_canvas.py # Live preview
│ │ ├── tool_palette.py # Manual editing tools
│ │ └── progress_dialog.py # Progress bar
│ ├── analysis/ # Terrain analysis
│ │ └── terrain_analyzer.py # Slope/aspect/statistics
│ ├── heightmap_generator.py # Core heightmap container
│ ├── noise_generator.py # Perlin/Simplex generation
│ ├── coherent_terrain_generator_optimized.py # FFT-optimized (9-14x faster)
│ ├── buildability_enforcer.py # Slope smoothing
│ ├── terrain_realism.py # Domain warping, ridges
│ ├── terrain_parameter_mapper.py # Parameter conversion
│ ├── worldmap_generator.py # Extended worldmap
│ └── cs2_exporter.py # CS2 integration
├── tests/ # 22 test files
│ ├── test_stage2_buildability.py # Buildability validation
│ ├── test_gradient_control_map.py # Gradient blending tests
│ └── verify_setup.py # Dependency checker
├── docs/ # Documentation
│ ├── analysis/ # Research & analysis
│ │ └── map_gen_enhancement.md # Evidence-based requirements
│ ├── features/ # Feature documentation
│ └── fixes/ # Bug fix documentation
├── examples/ # Example scripts
│ ├── 01_basic_usage.py # Getting started
│ ├── 02_preset_terrains.py # Using presets
│ ├── 03_with_worldmap.py # Heightmap + worldmap
│ └── 04_custom_terrain.py # Custom design
├── output/ # Generated heightmaps
├── requirements.txt # Python dependencies
├── ARCHITECTURE.md # Architecture documentation
├── CHANGELOG.md # Version history
├── TODO.md # Task tracking
├── CLAUDE.md # Development instructions
└── README.md # This file
from src.cs2_exporter import quick_export
quick_export(
heightmap_path='output/my_map.png',
map_name='My Custom Map',
worldmap_path='output/my_map_worldmap.png', # Optional
overwrite=True
)
- Generate and save your heightmap locally
- Copy PNG file to:
C:/Users/[username]/AppData/LocalLow/Colossal Order/Cities Skylines II/Heightmaps/
- In CS2, open Map Editor
- Go to Terrain Tools → Heightmaps → Import Heightmap
- Select your heightmap from the list
- Height Scale: Use 3000-5000m for dramatic mountains, 1000-2000m for gentle terrain
- Smoothing: Apply 1-3 smoothing iterations for natural appearance
- Combine Techniques: Layer noise with manual features for best results
- Test Iterations: Use lower resolution (1024) for testing, then generate at 4096
- Worldmaps: Add worldmaps to make your map feel more immersive
- Seeds: Save your seed values to recreate exact terrains later
The exporter automatically detects CS2's installation. If it fails:
from src.cs2_exporter import CS2Exporter
exporter = CS2Exporter()
exporter.create_cs2_directory() # Manually create directory
print(exporter.get_info()) # Check status
- Ensure file is 16-bit PNG (not 8-bit)
- Check resolution is exactly 4096x4096
- Verify filename doesn't contain invalid characters
- Restart CS2 if you added files while it was running
For faster testing:
# Use lower resolution for testing
terrain = create_preset_terrain('mountains', resolution=1024) # 4x faster
# Reduce octaves
noise_gen.generate_perlin(resolution=4096, octaves=4) # Instead of 6
# If noise library fails
pip install noise --force-reinstall
# If PIL/Pillow issues
pip install Pillow --upgrade
Actual Performance:
- 4096x4096 generation: <1 second (0.85-0.94s with FastNoiseLite)
- Smoothing: 2-5 seconds per iteration
- Worldmap generation: 1-2 seconds
- Preview rendering: 0.3-0.8 seconds
- Total workflow: Real-time interactive (was 2+ minutes in v2.0)
Critical Dependency:
- Requires
pyfastnoiselite
for optimal performance - Without it: Falls back to slow Python (60-120s per generation)
- Run
python verify_setup.py
to check installation
Hardware Recommendations:
- CPU: Any modern processor (2+ cores recommended)
- RAM: 4GB minimum, 8GB recommended
- Storage: SSD for faster export (optional)
- GPU: Not required (CPU-based generation)
This is a standalone tool. Feel free to modify and extend for your needs. Key areas for enhancement:
- Real-world elevation data import (SRTM, ASTER)
- GUI interface for visual editing
- Erosion simulation improvements
- Texture/biome map generation
- Batch processing capabilities
This tool is provided as-is for use with Cities Skylines 2. The code is based on standard terrain generation techniques and is free to use and modify.
- Terrain generation algorithms: Standard Perlin/Simplex noise implementations
- CS2 heightmap specifications: Cities Skylines 2 Paradox Wiki
- Development: Claude Code (Anthropic)
Version: 2.5.0-dev Last Updated: 2025-10-10 Compatible with: Cities Skylines 2 (all versions) Key Updates: Hybrid zoned terrain generation (Sessions 2-8), hydraulic erosion integration, 55-65% buildable terrain achieved, legacy system removed