DOMINATION

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)

  • terrainRows are 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 surgical terrainRows regex 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 + empty terrainRows) build terrain at runtime — skip terrain-shape checks for them.
  • Authored maps MUST set generatorParams.authoredFlatBase=true or 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.