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:
- Base generation —
GridMap.GenerateFromDescriptoruses the descriptor. Native production authoring deliberately setsgenerator: "none"andauthoredFlatBase: true; full terrain rows alone do not guarantee a flat/no-noise elevation base. - Tile overlays — full
terrainRows, then sparseoverlayTilesandterrainLayers. Rows establish the authored base grid, subject to those later overlays. - 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. - 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.
- World construction/ecology — objects, objectives, and vegetation create real
footprints and cover.
vegetationProfileand 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
wand wadeable shallowhcells interrainRows/overlayTiles, or a generator's river. Exact, deterministic, no noise. Both share one visible water surface;hexposes a lighter submerged shelf and excludes ships/whitecaps while remaining wadeable by infantry and amphibious units. - Elevation-carved water — the River Carve brush writes
wtiles and feathers banks viaelevOffsets. 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., noterrainRows): 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.5is 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 (
ktile), not the passable flag. - The Elevation tool clamps offsets to ±4; keep tactical high-ground
≤ 3.5; usekfor 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 explicitkcliff 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→Bis 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 atdh = 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. ItselevOffsetsstring is long (~14 KB) precisely because it is a dense wall of...,5entries. - Central walkable hill + rocky crown —
high_command(256×184). A smooth walkable dome (offsets ramp0.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.0mesas 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
wor wadeable shallowhinterrainRows(Terrain tool). Usehonly where ground traversal is intentional. River profiles are opaque, so their submerged gameplay shelf is deliberately hidden; naval units and open-water whitecaps still excludeh. Addm(mud) ors(sand) banks for readable shorelines — the generator'sPaintBanksdoes this automatically on preset maps. - River Carve brush (Elevation tool → River Carve): drag to carve a river — the core
band becomes
waterand the banks are feathered to an intermediate height viaelevOffsetsfor 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-extentsrx/ry(a2×2half-extent = 5×5 blob).ApplyResourceOverlaylevels 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. Afuel_depotis a fuel tank and two drum pallets that go up as one explosive site (radius 8.5 wu, burning ground after);barrelslays 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 (NeutralDestructibleCatalogexplosive 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,patrolflag) 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 withradius ≤ 0warn. - Triggers (
TriggerData): conditions + actions,targetIdmust resolve to a real entity id (spawn/zone/strategic/placed/waypoint/path/trigger). A trigger with no conditions and no actions warns; a danglingtargetIderrors.
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..2procedural 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).nightalso switches on warm emissive building-window glow (bloom-lit, no per-building lights). Empty = the noon default (unassigned maps are unchanged). SupersedestimeOfDaywhen both are set. Curated values live inAssets/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:
- Save + Validate in-editor (
V) — zero errors; justify every warning. unity/compile-check.sh— if any C# changed (0 errors).unity/run-tests.sh— logic invariants (placement + elevation round-trip) after any logic/data change; exits nonzero on failure.-executeMethod Build.ValidateAllMaps— sweeps the current discovered maps → rankeddocs/map-validation-report.md(skirmish first, by severity). Re-run after a batch.-executeMethod MapQualityAudit.CheckShippedMapQuality— the CI gate (zero shipped errors, no≥ 90%terrain clone).- Preview bake (§12) if the map is new or its terrain/spawns changed.
- Overview capture —
Build.CaptureMapOverviewAuthored -mapId <id> -out <path>, or aunity/build-game.sh+ numberedunity/shots/NN_description.pngfor the visual audit trail — verify the shot shows the change. - 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.
- 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:
elevOffsetshere is illustrative:...,4.0cells (> 3.5 cap) form a small cliff wall;...,1.2cells stay walkable high ground beside it — a readable ridge with a passable shoulder.- The
orehalf-extents (rx/ry: 3) give each base a 7×7 blob at ~mirror distance (fairness gap ≈ 0). The centralderrickis 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.