DOMINATION map-authoring cookbook
A practical, end-to-end guide to generating realistic, fair, fun skirmish maps systematically —
no Unity, everything in unity/tools/maptools/, every step under a second. This is the "one way to
generate a map" reference: pick a recipe, run the pipeline, gate it, ship it.
Maps are a char-grid + procedural mesh (NOT Unity Terrain): terrainRows (one char per cell),
per-cell elevOffsets, and placedObjects. Everything below edits that JSON deterministically.
The pipeline (in order)
paint blueprint.png ──▶ image_to_map ──▶ terrain_tiers ──▶ terrain_rivers ──▶ terrain_surface
(+ heightmap.png) (blueprint→map) (relief) (fair rivers) (rock/snow/lava)
│
map_schematic ◀── validate_maps + map_fairness ◀── dress_map ◀── forest_gen ◀── terrain_fields
(inspect / evidence) (gate) (rocks/scatter) (trees) (farmland + crop circle)
Not every map needs every step. Relief/surface for anything with mountains; rivers for a river map;
fields for farmland; forest/dress for density; always finish with validate_maps (+ map_fairness
on competitive maps) and a map_schematic render for the evidence trail.
One-command wrapper (land maps): generate_realistic_map.py <bp.png> --id X --name "X" --style basin
(styles: plains|valley|basin|mountains).
Tool reference
| Tool | Does | PR |
|---|---|---|
map_palette.py |
the terrain↔colour key (paint blueprints with these RGBs) | — |
image_to_map.py |
painted blueprint (+ optional --elevation heightmap) → validated map JSON. --mirror x/y/quad, --naval |
#2770 |
terrain_tiers.py |
coherent elevation: plains→hills→mountains, rim-biased. --clamp keeps ridges passable |
— |
terrain_surface.py |
paints rock/scree/snow/scorched onto elevation (stops mountains rendering as green grass) | #2748 |
terrain_rivers.py |
carves a fair river along the mirror axis with bridge/ford crossings | #2751 |
terrain_fields.py |
Midwest farmland: section-road grid + crop tiles + a 🛸 crop-circle easter egg | #2753 |
forest_gen.py |
biome tree/shrub clusters (fleet tool) | #2749 |
dress_map.py |
rocks on high ground + biome scatter + war-story clusters. --naval for island maps |
#2788 |
landmarks.py |
9 quirky drop-in easter eggs (see below) | #2762 |
generate_realistic_map.py |
one-command land pipeline with --style presets |
#2756 |
map_schematic.py |
render a map to a colour PNG (terrain + prop footprints + markers) — inspection/evidence | #2774 |
roster_overview.py |
the WHOLE roster as one contact sheet + greybox audit | #2783 |
validate_maps.py |
headless validity gate (dims, spawns, bounds, reachability, cartoonish assets) | #2761 |
map_fairness.py |
competitive-fairness gate (mirror symmetry) | #2786 |
audit_model_refs.py |
catches "renders as a grey cube" (unresolved model keys) | #2768 |
Recipes (by map type)
Blueprint colours are in map_palette.py; pins: red=spawn, magenta=ore, orange=derrick, yellow=tech,
cyan=radar. Paint only the canonical region for --mirror (left half for x, top-left quad for quad).
1. Realistic land skirmish (plains / valley / basin)
python3 generate_realistic_map.py bp.png --id my_map --name "My Map" --biome wilderness --mirror x --style basin
python3 validate_maps.py .../my_map.json && python3 map_fairness.py .../my_map.json
basin = mountains ringing a valley + river + farmland (the works); plains = gentle rolling + fields;
valley = hills + a central river; mountains = dramatic ranges, no farms.
2. Naval / archipelago
Paint water base + island discs (grass core → sand beach → shallow h landing ring); spawns on the
island interiors.
python3 image_to_map.py bp.png --id atoll --name "Atoll" --biome islands --base w --naval --mirror quad ...
python3 terrain_surface.py atoll.json --mirror quad
python3 dress_map.py atoll.json --mirror auto # --naval auto-detected from tags/water%
--naval skips the land-reachability requirement (sea is the connective tissue); verify the sea is
one connected water body. Great home for the sos landmark.
3. Farmland
python3 image_to_map.py bp.png --id farm --name "Farm" --biome wilderness --mirror quad ...
python3 terrain_tiers.py farm.json --clamp 2.2 --edge-bias 0.2 # gentle
python3 terrain_fields.py farm.json --parcel 32 # section grid + crops + crop circle
4. King of the Hill (plateau + ramps) — heightmap-authored
Paint a companion grayscale heightmap (same size, --elevation): brightness ~128 == the 3.5 walk
cap. Plateau top ≈ 108 (walkable high), a cliff ring ≈ 175 (impassable), ramp corridors ≈ 108
cut through the ring, low ground ≈ 22.
python3 image_to_map.py bp.png --id crown --name "Crown" --biome wilderness --elevation hm.png --mirror quad ...
python3 terrain_surface.py crown.json --mirror quad # rock the cliffs
This is how you author plateaus/mesas/ramps (terrain_tiers only does rim-mountains).
5. Urban cityscape
Paint pavement base + an asphalt street grid + grass parks. Then place blocking buildings in block
INTERIORS keeping a ≥3-cell passable pavement margin from streets (so the grid stays connected):
proven building keys are the usa_* civilian set (usa_lowrise_apartment, usa_building_1/2,
usa_warehouse, usa_water_tower, usa_communication_tower) + bunker_urban_garrison_house/shop.
validate_maps confirms connectivity with the blockers in place.
🎉 Fun quirky bits — landmarks.py
Drop a memorable oddity into any map (connectivity-safe, symmetry-aware):
python3 landmarks.py <map.json> --kind henge --at CX,CY --mirror x
python3 landmarks.py --demo gallery.png # contact sheet of all 9
crop_circle · kaiju_tracks · spiral_glyph (Nazca) · henge (standing stones) · meteor
(crater+ejecta) · boneyard (wreck Cadillac-Ranch) · smiley · maze (hedge maze) · sos (sand
distress sign, for naval maps). Reuses only catalog props.
Gate & inspect (always)
python3 validate_maps.py <map.json> # dims, ≥2 starts, bounds, reachability, cartoonish assets
python3 map_fairness.py <map.json> # competitive fairness (mirror symmetry)
python3 audit_model_refs.py # no unresolved model keys (whole project)
python3 map_schematic.py <map.json> --out shot.png # evidence render
python3 roster_overview.py --skirmish-only # see the whole playable roster
validate_maps / map_fairness / audit_model_refs exit nonzero on failure → drop-in CI gates.
Gotchas (hard-won)
terrainRowsare STRINGS (immutable). To stamp terrain:rows = [list(r) for r in d["terrainRows"]], edit, then"".join.- Three JSON formats coexist (indent=1, indent=2, compact single-line). A blind
json.dump(indent=1)reformats the odd ones into huge diffs. Use a surgicalterrainRowsregex replace (chars never contain]), or match the file's format. - Pins inherit the map BASE, which is water on island maps → a spawn/resource pin becomes "on impassable". Fixed in
image_to_map(pin cell now takes the nearest painted terrain). - Heightmap brightness → elevation: ~128 = the 3.5 walk cap. Below = walkable, above = impassable cliff.
- Fairness = mirror symmetry, NOT per-spawn resource-distance (a fixed BFS radius false-flags big maps). A resource whose exact mirror lands on water/ice is terrain-forced, not a bug.
- Campaign/procedural maps (a
generator+ emptyterrainRows) build terrain at runtime — skip terrain-shape checks for them. - Authored maps MUST set
generatorParams.authoredFlatBase=trueor biome-noise elevation stacks and disconnects spawns. - Naval maps: land-BFS can't judge them — spawns sit on separate islands crossed by ships; connectivity/fairness treat land-unreachability as informational.