DOMINATION

DOMI-NATION Map Authoring Guide

Guide to building and validating DOMI-NATION maps with the native editor. For current engine contracts, known discrepancies, and the evidence required to ship, read the shared map-building lessons. The reviewed 2026-09-04 root ships only Heartland (384 × 384, two players); enumerate the current worktree before using examples. Older named recipes and measurements below are historical, not the current roster or proof of current quality.

  • Editor: Tools → DOMI-NATION → Map Editor (DomiNationMapEditorWindow).
  • Schema (source of truth): unity/Assets/Scripts/Map/MapDescriptor.cs.
  • Load order + engine rules: unity/Assets/Scripts/Map/MapLoader.cs.
  • Validator (every rule): unity/Assets/Scripts/Map/MapValidation.cs.
  • Multiplayer packaging/approval: docs/custom-maps.md.

A map is one pure-data JSON file (MapDescriptor) in StreamingAssets/data/maps/<id>.json (built-ins), data/custom_maps/ (project custom), or persistentDataPath/maps/ (user). No scene edits — the descriptor is portable and diffable.


0. The mental model (read this first)

The runtime builds a map in a fixed order. Check MapLoader and its world-build caller:

  1. Base generation — GridMap.GenerateFromDescriptor uses the descriptor. Native production authoring deliberately sets generator: "none" and authoredFlatBase: true; full terrain rows alone do not guarantee a flat/no-noise elevation base.
  2. Tile overlays — full terrainRows, then sparse overlayTiles and terrainLayers. Rows establish the authored base grid, subject to those later overlays.
  3. Relief and alignment — additive elevOffsets, then optional built-in road-cut sidecar, then bridge deck alignment. Duplicate offsets add. Custom maps do not inherit a built-in road-cut sidecar merely by reusing its ID.
  4. Resource overlay — ore subsequently levels to walkable ground. It preserves road, bridge, rail, dam, concrete, and pavement cells; it can replace rock/water. Derricks are placed later as world entities. Include this order in offline reconstruction.
  5. World construction/ecology — objects, objectives, and vegetation create real footprints and cover. vegetationProfile and density are independent of biome.

Scan the whole terrainRows grid, not just sparse overlays, when auditing water/roads or other authored features. Inspect the final runtime tiles and heights when later stages can change them. Terrain-only reachability does not account for built-world blockers.

Two water systems coexist, and runtime truth is always map.tiles after MapLoader finishes:

  • Tile water — deep w and wadeable shallow h cells in terrainRows/overlayTiles, or a generator's river. Exact, deterministic, no noise. Both share one visible water surface; h exposes a lighter submerged shelf and excludes ships/whitecaps while remaining wadeable by infantry and amphibious units.
  • Elevation-carved water — the River Carve brush writes w tiles and feathers banks via elevOffsets. Still deterministic.

1. Create the map

Toolbar → New, or seed from the Generation Presets foldout.

Production skirmish sizes are exact and square:

tier dimensions
SMALL 184 x 184
MEDIUM 256 x 256
LARGE 384 x 384
XL 512 x 512
XXL / MASSIVE 1024 x 1024

The editor's Resize tier control offers only these values. New and redesigned skirmish maps use their tier exactly; arbitrary and rectangular skirmish dimensions are retired. Campaign maps, tutorials, and small logic fixtures are exempt until separately scheduled. Never shrink a tier to work around an engine limit; fix and measure the engine instead.

A. Blank canvas (MapSaveLoadService.CreateNew): the production UI defaults to SMALL 184 x 184 and supports every tier through 1024 x 1024. The low-level API still accepts smaller arbitrary grids for focused editor tests. Biome defaults to wilderness; generator: "none"; seed: 20260630 (fixed and deterministic). You get two starts (p1, p2), two ore blobs, one central derrick, and a full grass terrainRows grid to paint on.

B. Generation Preset (MapGenerationPresetCatalog) — a first-pass, editor-polish-ready map with terrain, roads, spawns, resources, strategic points, AI zones, waypoint paths, and production stamps already placed. 13 presets:

id tier / size players biome layout naval
balanced_duel SMALL 184 2 wilderness duel_crossing –
team_war_2v2 MEDIUM 256 4 wilderness lane_based –
large_battlefield_3v3 LARGE 384 6 wilderness radial_ffa –
ffa_4player MEDIUM 256 4 wilderness radial_ffa –
chaos_6player LARGE 384 6 volcanic radial_ffa –
naval_island_war MEDIUM 256 4 islands island_clusters ✓
river_crossing SMALL 184 2 wilderness river_divided –
urban_combat SMALL 184 2 city urban_grid –
mountain_pass SMALL 184 2 wilderness mountain_pass –
desert_oil_war MEDIUM 256 4 desert open_battlefield –
farmland_conflict SMALL 184 2 wilderness semi_symmetrical –
industrial_zone MEDIUM 256 4 city lane_based –
coastal_assault MEDIUM 256 4 lake center_contest ✓

Optional Theme and Layout overrides ((preset) = keep). "Create First Pass" builds in-memory; "Create, Save, Validate" also writes + runs the validator. The seed is stable per id:presetId, so regenerating the same map reproduces it byte-for-byte.

Metadata to set now (Map Settings / Environment foldout): name, author, description, recommendedPlayers (1–16). id is sanitized from the name (lowercase, [a-z0-9], _-collapsed) and MUST match the filename.

Set an explicit Vegetation profile and Vegetation density at the same time. Valid profiles are temperate, forest, farmland, grassland, wetland, coastal, tropical, alpine, arctic, arid, urban, volcanic, savanna, and war_torn. The preset generator assigns both automatically; every shipped descriptor must store both.

World Creator Professional is excluded from this project. DOMI-NATION does not use Unity Terrain heightmaps; use deterministic terrainRows, elevOffsets, and the native hill/mountain/ridge/cliff/plateau/valley/pass tools.


2. Terrain

Two data forms exist; use native authored rows for new production maps.

  • Legacy generator (generator: "wilderness" etc., no terrainRows): terrain is computed from biome + seed. Inspect the final grid; do not substitute this for the native authored production workflow or assume it places starts/buildings safely.
  • Authored terrainRows (generator: "none", full h×w grid): every cell is explicit. Exact, diffable, and the editor's preferred save format. This is what every polished skirmish map ships.

Paint with the Terrain tool. Brush shapes: Brush (round, size 1–9), Line (press-drag-release Bresenham with brush width), Rect (filled/outline), Fill (flood-fill). Eraser resets to the biome base (sand on desert, grass elsewhere). Right-click samples the terrain under the cursor. Feather Biome Edges softens hard biome borders.

Terrain character map

terrainRows uses one char per cell. Check MapSaveLoadService.TerrainCharMap() and its data-backed overload together: adding a char only to terrain.json does not make the no-data loader understand it. The reviewed root assigns f to both cropland and fence; the no-data mapping resolves cropland, but the data-backed map can overwrite it with fence. Verify editor/load/save agreement before relying on that character.

char id build pass speed use
g grass ✓ ✓ 1.0 biome base
d dirt ✓ ✓ 1.0 biome base
s sand ✓ ✓ 1.0 desert base, shorelines
k rock ✗ ✓* 1.0 cliffs — +4.5 elev → impassable (see §3)
r road ✓ ✓ 1.5 map-style road; auto junctions by adjacency
a paved_road ✓ ✓ 1.5 asphalt avenue (curbs/lane lines), any biome
q concrete_road ✓ ✓ 1.5 concrete slab / military route, any biome
w water ✗ ✗ — naval-only; ground blocked
h shallow ✗ ✗† 0.6 visible infantry/amphibious ford; vehicles/ships blocked
b bridge ✗ ✓ — engineered land crossing; blocks ships
o ore ✗ ✓ — harvestable (usually from resourceNodes, not painted)
c concrete ✓ ✓ 1.25 base pads, industrial yards
p pavement ✓ ✓ 1.4 bridge approaches, aprons, parking
m mud ✗ ✓ 0.65 river banks, swamp — slows pushes
i ice ✗ ✓ 0.85 frozen crossing: ground units walk it, ships blocked
x scorched ✓ ✓ 1.0 battle damage, burn scars, wrecks
f cropland ✓ ✓ 1.0 intended farm terrain; mapping discrepancy described above

* rock is passable by the terrain.json flag but impassable in practice because ApplyTile bumps its elevation +4.5, over the MaxWalkElev 3.5 walk ceiling. Rock is the correct tool for an intentional un-climbable wall — do not use the passable flag for walls.

† shallow is not generally passable: movement-domain rules admit infantry and amphibious units only. Ordinary ground vehicles and naval units refuse it.

Note ice is passable to ground but water-blocked, so ice fords double as land routes on frozen maps; ships still need open w.


3. Elevation (elevOffsets)

Authored height sculpting layered over the derived biome/rock/water elevation. Sparse RLE string, one entry per non-zero cell:

"gx,gy,dh;gx,gy,dh;..."

dh is an additive float delta (parsed by MapLoader.ParseElevOffsets; malformed entries are silently skipped). Applied additively after tile overlays/layers, so duplicates stack, but road cuts, bridge alignment, and ore leveling follow (§0). Absent/empty means no authored offset. Verify final runtime state and the applicable identity checks.

New maps created by the editor and generation presets set generatorParams.authoredFlatBase: true. Their authored canvas starts at elevation 0, so offsets avoid hidden biome noise. Tile effects (rock/water), overlays, road cuts, bridge alignment, and ore leveling still apply; offsets alone are not every cell's final height. Legacy maps can leave the field false and retain their generated elevation.

The cliff rule (memorize this)

Walkability is an absolute cap, not a per-step climb limit:

Legal land elevation requires final height ≤ GridMap.MaxWalkElev (3.5).

This is only the elevation test. Domain-specific terrain rules and blocked footprints still apply, and standing on a cell does not prove an end-to-end route.

There is no slope/step check — units cross any height difference between two cells as long as both stay ≤ 3.5. The real hazard is an offset that raises walkable ground above 3.5, turning it into an accidental impassable wall units route around. Consequences:

  • A final height above 3.5 blocks ordinary land movement; check later leveling operations before treating an offset alone as a guaranteed wall.
  • base_max + dh ≤ 3.5 is a conservative elevation bound only when the actual base and all other height modifiers are included; terrain legality and footprints remain separate.
  • Rock cliffs come from the +4.5 elevation bump (k tile), not the passable flag.
  • The Elevation tool clamps offsets to ±4; keep tactical high-ground ≤ 3.5; use k for walls. The validator warns on over-cap sculpted cells (see §11, CheckElevationSeams).
  • A sharp edge between two ordinary walkable cells is visually misleading because units can cross it. The validator warns when that walkable seam exceeds 1.75; smooth it or mark the edge with an explicit k cliff contour.

Brushes (Elevation tool)

Raise/Lower (±strength 0.1–2, size 1–8), Flatten (pull footprint toward the hovered cell's height), Smooth (3×3 mean), Ramp (A→B) (click LOW end then HIGH end — writes a linear walkable slope, refused if it would sever the start-to-start path), River Carve (§4). The canvas tints raised cells red / lowered blue.

Native landform generation

Choose Elevation → Landform for deterministic authored forms:

  • Hill: one-click smooth, fully walkable dome.
  • Mountain: one-click rugged foothills with an explicit rock crown.
  • Plateau: one-click flat tactical top with a rock escarpment ring. Roads remain openings.
  • Ridge: two-click elongated walkable high ground.
  • Cliff: two-click directed escarpment. A→B is the rock edge; its left side is raised.
  • Valley: two-click lowered corridor, clamped above the visual water plane.
  • Pass: two-click mountain ridge with a wide center gap and infrastructure openings.

Height, radius/half-width, roughness, and seed are authored controls. Enable Preserve bases, resources, objectives, roads, and rail passes for production work. The apply is refused and fully restored if the candidate disconnects player starts.

The same panel offers full-map templates: flat, rolling_hills, highlands, mountain_chain, plateaus, canyon, coastal_bluffs, and mixed. Templates use proportional paired features, record their seed/style in generatorParams, bake exact elevOffsets, preserve gameplay pads, and remain editable with every other elevation brush. Generation Presets expose the same Relief selector, so generated first passes contain real terrain forms instead of terrain-character-only rock stripes.

Historical recipes (July 2026, not the current roster)

These measurements explain earlier patterns, not current acceptance. Their rectangular dimensions and named maps are retired examples; use canonical tiers and a fresh layout.

  • Twin-ridge divide — frontier_divide (256×192, wilderness). Two 5-wide walls at dh = 5.0 (well over the 3.5 cap → guaranteed cliffs), each pierced by 3 staggered 13-wide passes. All 8 spawns / 16 ore / 10 derricks stay BFS-connected through the passes. This is the canonical "ridge with gaps" pattern: build the wall over-cap, then carve wide passes back under-cap. Its elevOffsets string is long (~14 KB) precisely because it is a dense wall of ...,5 entries.
  • Central walkable hill + rocky crown — high_command (256×184). A smooth walkable dome (offsets ramp 0.2 → ~0.95, all under cap) with a rock (k) crown for the un-passable peak and a 4-garrison town at the foot. Uses sub-1.0 offsets to shape readable high ground you can still fight on.
  • Mesas — sandstorm_flats (WEAK, learn from it): dh = 7.0 mesas parked in opposite corners create no corridor pinch — high ground with no tactical purpose. Lesson: elevation must gate a route or command a contested lane, not decorate a corner.

4. Water + River Carve

  • Painted water: paint deep w or wadeable shallow h in terrainRows (Terrain tool). Use h only where ground traversal is intentional. River profiles are opaque, so their submerged gameplay shelf is deliberately hidden; naval units and open-water whitecaps still exclude h. Add m (mud) or s (sand) banks for readable shorelines — the generator's PaintBanks does this automatically on preset maps.
  • River Carve brush (Elevation tool → River Carve): drag to carve a river — the core band becomes water and the banks are feathered to an intermediate height via elevOffsets for a soft shoreline. River Width 1–8 tiles, feathered edges. Undo-able. Span it with the Bridge tool.

Water profiles and currents

The runtime editor's left palette selects a map-level water profile and previews it immediately. Dir -15/+15 rotates the presentation-only heading: prevailing wind for ocean/coastal/lake profiles, and downstream current direction for rivers/marsh. Speed -/+ adjusts the 0..2.5 propagation multiplier. Motion -/+ adjusts waterMotionScale from still (0) through the profile default (1) to the supported dramatic maximum (2). The renderer derives a compact three-band gravity-wave spectrum from those values; river bands remain aligned downstream, while open-water bands spread around the prevailing wind. These values save with waterProfile, waterFlowAngle, and waterFlowSpeed. They never affect pathing, naval steering, projectiles, terrain checksums, or lockstep simulation.

Profile Visual identity Motion / shore treatment
ocean Deep blue, stronger horizon response Crossed moving swell faces, broken crest arcs, full travelling surf
coastal Brighter blue-teal shallows Moderate swell, broken crests, and coastal wash
lake Calm blue-green, nearly opaque Small moving ripples, almost no cresting, gentle shore wash
clear_river Dark natural alpine teal, opaque Continuous flow lanes, downstream ripple packets, restrained bank wash
silt_river Lighter tannin/sediment brown, opaque Softer downstream flow lanes and packets, warm low bank wash
marsh Dark olive-green, opaque Near-still slow drift and a quiet moving bank edge

If waterProfile is absent, legacy maps infer a profile from biome: islands → coastal, lake → lake, wilderness → clear river, city/desert → silt river, and other liquid maps → ocean. Snow and volcanic maps continue to use their dedicated ice and lava materials.

Disconnected player starts produce a connectivity error. A land route can go around the water or use a legal crossing. The water-map alternate-route check emits a warning: it removes the interior of one found path and tries another. Treat it as a design diagnostic, not a complete graph-theoretic proof that no two independent routes exist. Inspect and test the intended alternatives so one lost bridge does not sever play.


5. Roads (the network tool)

Road tool: pick a type — map-style (r, dirt/asphalt/concrete by biome or roadSurface), asphalt avenue (a), or concrete slab (q) — and a width (1–2), then drag to lay road along the mouse path (fast drags stay continuous). The renderer picks straights / turns / T-junctions / crossings / plazas from adjacency automatically at render time (real HD Roads tile atlas — the editor canvas matches the shipped 3D look; toggle Road Tex). Water cells under the drag become bridge automatically, so dragging across a river lays the crossing. Right-click erases (road → biome base, bridge → water). Roads never pave over ore.

Design intent: connect every start to the contested center and to its expansions. A road that dead-ends on dry land away from the map edge is flagged (§11) — end roads at a destination (base, building, resource, objective), at the map edge, or at a shoreline.


6. Bridges

Bridge tool: click one bank (anchor), then the far bank — the tool validates and stamps a clean rectangular deck across the water plus a short road approach onto each bank.

New editor bridge limits (MapAuthoringTools.PlanBridge):

  • Span length at least 3 cells and a real water crossing.
  • Deck width 3–6; the editor slider uses BridgeMinWidth/BridgeMaxWidth.
  • Separate, filled, axis-aligned rectangles with clear full-width bank/road approaches.
  • Touching/merging another deck is refused. Near-axis clicks can snap; diagonal authoring is refused by this tool.

Existing runtime geometry also supports certain clean diagonal bands. Inspect BridgeTerrainGeometry and MapValidation before repairing those; authoring acceptance, renderer classification, and fallback appearance are separate. Do not loosen geometry rules merely to force a modular render. Deck alignment uses approach elevations, so grade and inspect both approaches and verify unit travel height.

Bridge tiles block ships. Verify intended water components, actual yard placement, production/launch, and naval transit. A bridge mesh does not imply ships can pass under it.

Roads may use runtime geometry or an existing Resources/RoadMesh/<id> overlay generated by RoadBake; inspect which path loads. Gameplay remains in the terrain cells. Old notes that EasyRoads integration is merely proposed are historical; new pack imports are a separate implementation task, not required just to use the existing renderer.


7. Spawns, resources, strategic points (fairness)

Start tool: place player starts p1..pN. Requirements: ≥ 2 starts and ≥ recommendedPlayers. The prefix az_ is reserved for campaign wave anchors (amphibious / scripted) and is filtered out of skirmish starts and the ≥2 count. A p* start force-spawns the base HQ unflattened (no CanPlace gate, no footprint flatten), so it MUST sit on buildable land — water floats it, rock clips it, and the base can't expand.

Per-start metadata (selected-start inspector): Team (0 = FFA/auto; same number = allied), Starting Credits (0 = match setting), Facing (deg 0–359, -1 = auto). Team/credits/ facing honoring in the sim is gated behind the multiplayer-sync pass; the fields are authored-and-validated now.

Resource tool:

  • ore — a harvestable blob, half-extents rx/ry (a 2×2 half-extent = 5×5 blob). ApplyResourceOverlay levels ore to walkable ground even over rock/water.
  • derrick — a capturable oil derrick (a point; no tile stamp). Must be ground-reachable from a start or it errors (#1939 shipped 7 land-locked derricks before this rule).
  • Strategic points (StrategicPoint.type): garrison, repair, tech, tech_objective, derrick, barrels, fuel_depot. A fuel_depot is a fuel tank and two drum pallets that go up as one explosive site (radius 8.5 wu, burning ground after); barrels lays the two drum pallets alone. Keep either > 13 cells from every spawn (the engine placer skips closer cells) and verify its 3×2 block doesn't sever a lane. Placed gas stations, fuel tanks, tank farms, chemical plants, ammunition stores and grain silos explode too (NeutralDestructibleCatalog explosive table, GameController.ExplosiveSites): site them where a blast is a tactical choice, not at a spawn.

Fairness norms (per-spawn nearest-node Euclidean distance; enforced + audited):

  • Every start needs ore within 34 tiles (else warning).
  • Nearest-ore spread across starts must be ≤ 10 tiles (else "uneven resource access").
  • Mirror resource value across starts on competitive maps. The target is a fairness gap ≤ 13; strong maps sit near 0.
  • Two starts < 24 tiles apart warns; starts within 10 tiles of a map edge warn.

8. Props (grounded catalog)

Object tool: place buildings/defenses/units by faction def, or catalog props via the asset picker (grouped, environment-filtered, thumbnailed, searchable — backed by MapEditorAssetCatalog). Every catalog key is auto-baked into the player build (BakeModelDB → AllModelKeys()), so adding a catalog entry is the complete wiring.

Scatter Brush (prop/tree/rock): density-paints — radius 2–12, 1–20 per click, random rotation, ±20% size jitter, one per cell, and skips water/shallow water, every road surface, bridges, ore, concrete/pavement, ice, rail, dams, occupied cells, and blocking placements inside every start's 8-cell build halo. Enable Profile Foliage to draw only quality-approved keys from the map's ecological profile. The selected asset's size, fit-height, and pathing metadata are applied per randomly chosen key. Select box-selects (Shift = additive) for Ctrl+C/V copy-paste at the cursor and Del/Ctrl+D.

Vegetation quality gate. Trees and ground cover shown by the production palette must pass MapEditorAssetCatalog.IsApprovedVegetationKey and match the active profile. Rejected legacy models remain resolvable for migration but cannot be newly placed. The failed agriculture_farm_*field_tile*/orchard meshes are quarantined until reviewed replacements are bound; the audit reports every remaining instance.

Banned keys — hard ERROR at save + CI. The Polytope medieval-fantasy dock series (prefab_polytope_dock_*) and recolored grass tufts (Grass_Tall_Orange/White, Grass_Sea) are rejected (MapEditorAssetCatalog.IsBannedModelKey). Swap for grounded catalog keys (containers, gas tank, cranes, patrol boats, real trees/grass). Art direction: clean low-poly stylized-realistic with coherent PBR materials — no cartoon *_Prefeb keys, melted Meshy geometry, incompatible shader packs, or raw scans that are unreadable at RTS distance.

Density / variety norms (density = props per 10 000 tiles):

  • ≥ 10 props/10k tiles — below that a skirmish map reads bare (validator warns under 10 total placed objects; tutorial maps exempt).
  • ≥ 12 distinct model keys — low variety (7–9 keys) reads repetitive.
  • Fairness gap ≤ 13 (per §7).

Spawn clearance: keep scatter and blocking props ~8 tiles clear of every start. The scatter brush enforces this for blocking assets; hand placement still requires review. A blocksPathing prop inside a start's ~10×8 HQ pad + build halo cripples base buildout. Keep blocking props out of contested lanes narrower than ~4 cells; deep inside a 5×5+ road plaza is fine. A land prop dropped on open water warns as "floating."


9. Zones, waypoints, triggers (campaign)

  • Waypoint tool → named points (WaypointData); chain them into waypoint paths (WaypointPathData, domain: ground|naval|air, patrol flag) for attacks/patrols/ reinforcements. Every path needs an id and ≥ 2 waypoints, and each referenced waypoint must exist.
  • Zone tool → AI base/build/defensive/expansion zones (AIMapData) and campaign trigger areas (MapZone, circular {id,x,y,radius}). Convention: zone_* = trigger areas, wp_* = escort waypoints. Custom maps warn if they have no AI base zones or no attack paths. Zones with radius ≤ 0 warn.
  • Triggers (TriggerData): conditions + actions, targetId must resolve to a real entity id (spawn/zone/strategic/placed/waypoint/path/trigger). A trigger with no conditions and no actions warns; a dangling targetId errors.

Skirmish maps leave these empty — the format is purely additive, so a map without triggers/zones/waypoints deserializes exactly as before.


10. Weather + environment

Map Settings / Environment foldout:

  • Vegetation profile (vegetationProfile): the ecological runtime/editor palette listed in §1. Unknown values are validation errors; legacy empty values are inferred only for backward compatibility.
  • Vegetation density (vegetationDensity): 0..2 procedural plant multiplier. It scales trees, understorey, meadow tufts, wetland/coastal weighting, and city trees, but never deletes or duplicates explicit placed objects.
  • Weather (weather): (biome default) / clear / rain / fog / snow / ash. Empty = biome-implicit (snow→snowfall, volcanic→ashfall).
  • Sight multipliers (GameController.weatherSight, feeds FogOfWar reveal + unit acquisition): fog 0.70, rain/storm 0.80, everything else (clear/snow/ash) 1.0. Fog and rain literally shrink vision — a fog map rewards scouting and short sightlines; the HQ stays lit in any weather but forward structures see less.
  • Road surface (roadSurface): auto (city→asphalt, else dirt) / dirt / asphalt / concrete.
  • Time of day (timeOfDay): day / dawn / noon / dusk / night — repositions + retints the sun (night keeps lifted ambient for readability).
  • Lighting (lighting): noon (default) / dawn / dusk / overcast / night — a curated preset that bundles sun angle+colour+intensity+shadow-strength, Trilight ambient, and a fog-density MULTIPLIER (it modulates the biome/weather fog, doesn't replace it). night also switches on warm emissive building-window glow (bloom-lit, no per-building lights). Empty = the noon default (unassigned maps are unchanged). Supersedes timeOfDay when both are set. Curated values live in Assets/Scripts/Engine/LightingPresets.cs. Presentation-only — never touches the sim (lockstep-safe). Shipped assignments: caldera_protocol/hellfire_peninsula = dusk, district_zero/midnight_sun = night, frozen_divide = dawn, ashfall_perimeter = overcast.
  • Fog density (fogDensity): dial haze independently of weather (-1 = auto).

11. Validate — every rule

Toolbar → Validate (V), or headlessly -executeMethod Build.ValidateAllMaps. Results list is clickable (focuses the offending cell). MapValidator.Validate runs the full skirmish rule set; ValidatePlacementOnly runs the biome-agnostic subset for campaign maps. The CI gate MapQualityAudit.CheckShippedMapQuality fails the build on any shipped-skirmish error and on any terrain near-clone ≥ 90%.

Below is every rule, its exact message (abbreviated), what it means, and the fix. ~21 error rules + ~19 warning rules across 20 check functions.

Errors — must fix (block save-quality + CI)

# Message (abbrev.) Means Fix
E1 Map id is missing / Map name is missing Empty identity field Set id/name (id = filename).
E2 terrainRows height does not match map height terrainRows.Length != h Resize rows to exactly h.
E3 terrainRows[y] width does not match map width A row's length != w Pad/trim row y to w chars.
E4 ... is outside map bounds A spawn/resource/strategic/placed/waypoint/zone cell is off-grid Move it inside 0..w-1 / 0..h-1.
E5 At least two player start positions are required < 2 p* starts Add starts until ≥ 2 and ≥ recommendedPlayers.
E6 Duplicate <kind> id: X Two spawns/objects/waypoints/triggers share an id Make ids unique.
E7 Waypoint path 'P' references missing waypoint 'W' Path lists a non-existent waypoint Add the waypoint or fix the ref.
E8 AI <attack/patrol> path references missing waypoint path: P aiData names a path that doesn't exist Define the path or drop the ref.
E9 Trigger 'T' references missing id 'X' Trigger condition/action targetId unresolved Point it at a real entity id.
E10 Building 'B' is on non-buildable '<tile>' terrain Placed building fails CanPlace (water/rock) Move onto buildable land.
E11 Placed building/unit references missing definition: D defId not in any faction roster Fix defId.
E12 Unit placed on blocked terrain: D Unit on impassable/occupied cell Move to passable ground.
E13 Player start X is on '<tile>' terrain p* start on water/bridge/rock Move to buildable land (az_ exempt).
E14 No ground path between starts A and B A* finds no route (connectivity) Add a bridge/ford/land route.
E15 Bridge at (x,y) is not connected to land on either end Floating bridge (0 land contacts) Land both ends on buildable ground.
E16 Placed object uses banned model key 'K' Polytope/recolored-grass cartoon key Swap for a grounded catalog key.
E17 Oil derrick at (x,y) is unreachable by ground from any player start Land-locked derrick Connect by land/bridge or relocate.
E18 Strategic point 'S' at (x,y) is unreachable by ground Land-locked capture point Connect or relocate.
E19 Map generation failed: ... Generator/overlay threw Fix the descriptor field named in the exception.
E20 Unknown vegetationProfile ... / vegetationDensity ... outside 0..2 Invalid ecological metadata Choose a supported profile and density.

Warnings — fix or justify

# Message (abbrev.) Means Fix
W1 Map is very small for RTS play w < 48 or h < 36 Enlarge (§13).
W2 Start X is close to the map edge Edge distance < 10 Pull the base inward.
W3 Recommended player count exceeds authored start locations recommendedPlayers > p* count Add starts or lower the count.
W4 Starts A and B are very close (N tiles) < 24 tiles apart Space starts out.
W5 Waypoint path has no id / ... fewer than two waypoints Malformed path Give it an id + ≥ 2 waypoints.
W6 Trigger 'T' has no conditions or actions / missing an id Empty/anonymous trigger Fill it in or delete it.
W7 overlayTiles use undefined terrain 'T' (N cells) Semantic overlay id not in terrain.json Define it (buildable:false, passable:false, color) in terrain.json.
W8 Sculpted terrain at (x,y) rises to N, above the walkable cap (3.5) An elevOffsets cell became an accidental cliff Lower the sculpt ≤ 3.5, or paint k for an intentional wall.
W9 Terrain layer is N% identical to shipped map 'M' Near-duplicate (≥ 90%) of another shipped map Rework the terrain or retire one.
W10 Snow-biome map has N liquid water cells but zero ice tiles Frozen map with open water, no ice Paint ice sheets/fords at crossings.
W11 Crossing at (x,y) narrows to N passable tile(s) Bridge/ice ford < 3 wide → single-file funnel Widen to ≥ 3 tiles.
W12 Start X reaches no road tile and no other start Spawn cut off from the network Connect with a road/passable route.
W13 Water map has only a SINGLE ground route between starts A and B One choke; a lost bridge splits the map Add a second vertex-disjoint route.
W14 Only N placed object(s) — under 10 reads bare Prop density floor Dress the battlefield (§8).
W15 Bridge cluster at (x,y) fills only .../ is NxM Degenerate deck → per-tile procedural fallback Stamp a clean filled rectangle (Bridge tool).
W16 Start X has no authored resource within 34 tiles No ore/derrick near a base Add a resource in range.
W17 Uneven resource access: start X is N tiles ... but Y is only M Ore spread > 10 tiles between starts Balance nearest-ore distances.
W18 Road dead-ends at (x,y) Road stub on dry land, ≤ 1 road neighbor, not at a destination/edge/shore Extend it or end it at a destination.
W19 Bridge at (x,y) dead-ends into water Only one land approach Land the far end.
W20 Placed prop references a missing model key: K / negative target size Bad prop reference Fix the key / targetSize.
W21 Land prop 'K' sits on open water and will look like it's floating Terrestrial prop on w Move to land, or use a dock/ship/bridge asset.
W22 Custom map has no AI base zones / no AI attack path hints AI has no authoring hints Add base zones + attack paths.
W23 AI <zone> 'Z' has no radius radius ≤ 0 Give the zone a radius.
W24 Start X facing N° is out of range Facing ≥ 360 Use 0–359 or -1.
W25 Team/credit sanity: lopsided (NvM) / no team while others assigned / all starts share one team / credits differ ... Spawn-metadata inconsistency Make teams even, assign all or none, keep credits consistent per team.
W26 N placed vegetation instance(s) use unreviewed/legacy model K Asset is hidden/quarantined from production authoring Replace it or add a reviewed species binding after visual QA.
W27 N placed vegetation instance(s) ... outside the map's profile Ecological mismatch Replace it or document the intentional landmark planting.

12. Preview bake

The skirmish map-select UI shows a baked top-down PNG with numbered spawn markers. In-editor: Regenerate Minimap Preview. For the whole roster, driver-run offline (SkirmishMapPreviewBaker.GenerateAllSkirmishPreviews) — one full BuildWorld per map, writes Assets/Resources/MapPreviews/<id>.png (512-long-side), logs MAPPREVIEW markers, exits nonzero on any failure:

ED=/home/jesse/Unity/Hub/Editor/6000.5.0f1/Editor/Unity
flatpak run --command="$ED" com.unity.UnityHub -batchmode -projectPath unity \
  -executeMethod SkirmishMapPreviewBaker.GenerateAllSkirmishPreviews -logFile -

Do not pass -nographics (the capture pipeline SIGSEGVs on shadow maps without a GPU context). The UI falls back to the live MinimapPreview schematic when a PNG is missing.


13. Offline connectivity proof + enlargement

Offline connectivity checks (no Unity)

Reconstruct the actual loader order, including base/tile elevation, offsets, overlays, road cuts, bridge alignment, and ore exclusions/leveling. Full terrain rows are not by themselves proof that all elevation is explicit. A pessimistic noise bound can support an offline check only when it bounds the current implementation; do not reuse old biome amplitudes blindly.

Check domain legality and relevant blocked footprints, then complete paths between starts, resources, objectives, and alternate routes. An offline model should state which runtime objects or mechanics it omits. PassableFor is terrain legality and Reachable is cell occupiability; neither is an end-to-end path proof. Verify actual built-world pathfinding, group movement, placement, and production before accepting gameplay quality.

Enlargement (+dx/+dy translate)

For an explicitly requested migration of an existing map to a canonical tier (historical high_command/tidal_basin translation pattern), pad terrainRows with base-terrain (g) on the new margins, and translate every coordinate field by the same +dx/+dy so the authored region keeps its internal geometry — spawnPoints, resourceNodes, strategicPoints, placedObjects, waypoints, zones, every gx/gy in elevOffsets, aiData, and mapModules, and each x/y in zones. Then byte-compare the authored (untranslated) region against the original to prove nothing moved relative to itself, and re-run the BFS proof + Validate. Miss one coordinate list and you get a silent desync between terrain and objects.


14. Driver gate checklist (ship it)

Documentation-only edits need link, metadata, consistency, and diff validation. For map/data or runtime changes, use the relevant verification ladder; coordinate all Unity work per project path and keep evidence outside Unity Temp:

  1. Save + Validate in-editor (V) — zero errors; justify every warning.
  2. unity/compile-check.sh — if any C# changed (0 errors).
  3. unity/run-tests.sh — logic invariants (placement + elevation round-trip) after any logic/data change; exits nonzero on failure.
  4. -executeMethod Build.ValidateAllMaps — sweeps the current discovered maps → ranked docs/map-validation-report.md (skirmish first, by severity). Re-run after a batch.
  5. -executeMethod MapQualityAudit.CheckShippedMapQuality — the CI gate (zero shipped errors, no ≥ 90% terrain clone).
  6. Preview bake (§12) if the map is new or its terrain/spawns changed.
  7. Overview capture — Build.CaptureMapOverviewAuthored -mapId <id> -out <path>, or a unity/build-game.sh + numbered unity/shots/NN_description.png for the visual audit trail — verify the shot shows the change.
  8. Player/gameplay evidence — inspect normal built-player default/close views and verify real construction, route widths, objectives, production, and naval/AI behavior as applicable. An editor overview or validator pass alone is insufficient.
  9. Review the actual diff and evidence before merge. Keep workers isolated and preserve concurrent changes; never use a broad reset to clean another session's edits.

Historical schema mini-example (not a production recipe)

This July example illustrates descriptor fields only. Its 120 × 88 dimensions, abbreviated rows, retired source map, and copied field shapes are not valid new-production guidance. Use canonical square tiers and a fresh layout; inspect current schema/load rules above.

{
  "id": "twin_ford",
  "name": "Twin Ford",
  "description": "Two river fords bracket a contested central town.",
  "author": "DOMI-NATION Tools",
  "recommendedPlayers": 2,
  "biome": "wilderness",
  "vegetationProfile": "wetland",
  "vegetationDensity": 0.80,
  "weather": "clear",
  "roadSurface": "dirt",
  "w": 120,
  "h": 88,
  "generator": "none",
  "seed": 20260710,
  "tags": ["skirmish", "1v1", "2p", "wilderness", "authored_terrain"],
  "terrainRows": ["gggg...gggg", "gggg...wwww", "...(88 rows, each 120 chars)..."],
  "elevOffsets": "58,40,4.0;58,41,4.0;59,40,4.0;62,40,1.2;62,41,1.2",
  "spawnPoints": [
    { "id": "p1", "gx": 18, "gy": 66, "label": "South Base", "team": 1, "facing": -1.0 },
    { "id": "p2", "gx": 100, "gy": 22, "label": "North Base", "team": 2, "facing": -1.0 }
  ],
  "resourceNodes": [
    { "type": "ore", "gx": 26, "gy": 58, "rx": 3, "ry": 3 },
    { "type": "ore", "gx": 94, "gy": 30, "rx": 3, "ry": 3 },
    { "type": "derrick", "gx": 60, "gy": 44 }
  ],
  "strategicPoints": [
    { "id": "center_garrison", "type": "garrison", "gx": 60, "gy": 40, "label": "Central town garrison" }
  ],
  "placedObjects": [
    { "id": "landmark_001", "category": "prop", "type": "landmark", "modelKey": "RadarStation",
      "faction": "neutral", "gx": 60, "gy": 36, "rotation": 0, "targetSize": 6.0 }
  ]
}

Field notes:

  • elevOffsets here is illustrative: ...,4.0 cells (> 3.5 cap) form a small cliff wall; ...,1.2 cells stay walkable high ground beside it — a readable ridge with a passable shoulder.
  • The ore half-extents (rx/ry: 3) give each base a 7×7 blob at ~mirror distance (fairness gap ≈ 0). The central derrick is the contested prize.
  • This abbreviated historical snippet is not executable map data or current validator evidence. Real maps require complete rows and the full gameplay/player review above.

Cross-references: docs/custom-maps.md (multiplayer packaging/approval), and docs/skirmish-map-audit.md (archived in jessedye/war) (per-biome dressing recipes).

Grass appearance presets

vegetationSettings is an optional presentation object shared by gameplay, the standalone editor, Unity previews, templates and playtests. Omit it to preserve an older map's appearance. In the standalone editor choose File → Grass and planting, choose Meadow, Parkland or Northern, then Apply. Unity's map settings and inspector expose the same values. The standalone dialog is one undo step; Cancel leaves the document untouched.

Field Range Original value
grassDensity 0–2 1
meadowHeight 0.08–1.2 metres 0.42
lawnHeight 0.08–1.2 metres 0.20
flowerAmount 0–1 1
mowParks boolean false

Density multiplies the map's existing vegetation density. Zero hides grass while retaining trees, crops and gameplay cover. Mowing uses existing P park districts; ground texture and blade height share the same classification. Snow burial, road masks, fog, quality LOD and construction clearing still apply. Changing these values does not alter map terrain, pathfinding, cover or procedural tree placement.

Park and woodland shrubs

vegetationSettings.woodlandDensity and parkShrubs each range from 0 to 2. They control low decorative shrubs independently of trees and gameplay cover. Park districts use the park value; other districts use the woodland value. Shrubs form pockets beside park trees and taper at woodland edges. Open lawns, roads, water, steep banks, structures and start reserves stay clear. Deep snow buries the low plants. Existing unconfigured maps retain their legacy undergrowth.

In the standalone editor, drag a rectangle with Select, then open File → Grass and planting. Choose a preset and use Selection → park or Selection → woodland. The Unity editor exposes the same district operation with an origin and size. Place trees using the existing foliage tools; marking a district does not plant trees. Both district and appearance edits support undo and native save/reload.