Pipeline Reference¶
This page documents the optional build pipeline steps that veaf-tools mission build can run after generating veaf-config.lua. Each step injects data into the .miz file from a separate YAML configuration file.
Overview¶
The pipeline runs the following optional steps, in this order:
| Step | Config file | What it does |
|---|---|---|
presets |
src/presets.yaml |
Injects radio frequency presets into human-piloted aircraft groups |
waypoints |
src/waypoints.yaml or waypoints.yaml |
Injects waypoint templates into human-piloted aircraft groups |
spawnable_aircrafts |
src/spawnables.yaml |
Injects spawnable aircraft groups (veafSpawn- prefix, cloned by veafSpawn) |
dynamic_slot_templates |
src/dynamic-slot-templates.yaml |
Injects dynamic-slot templates (dynSpawnTemplate = true, consumed by DCS) |
warehouses |
src/warehouses.yaml |
Wires the warehouses for Dynamic Slots |
spawn_data |
src/spawn-groups.yaml (optional) |
Injects the spawn database (_spawn unit/_spawn group); always on (framework data is embedded) |
weather |
src/versions.yaml or versions.yaml |
Creates multiple mission variants with different weather and time |
Each step is auto-detected: it runs if its default config file exists. You can override this behaviour in mission.yaml. Exception: spawn_data always runs (even with no mission file) to embed the framework spawn database; src/spawn-groups.yaml only extends it.
Controlling the Pipeline (mission.yaml)¶
pipeline:
presets: false # skip even if src/presets.yaml exists
waypoints: true # auto-detect: runs only if the config file exists
spawnable_aircrafts:
file: src/my-spawnables.yaml # non-default file path
mode: replace # add (default) | replace
dynamic_slot_templates: false # skip dynamic-slot-template injection
spawn_data: false # disable spawn-database injection (framework included)
weather: false # skip weather variants
pipeline: fields¶
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
presets |
bool | object | auto | No | true/unset = auto-detect (run if file exists), false = always skip, object = custom options |
waypoints |
bool | object | auto | No | true/unset = auto-detect (run if file exists), false = always skip, object = custom options |
spawnable_aircrafts |
bool | object | auto | No | true/unset = auto-detect (run if file exists), false = always skip, object = custom options |
dynamic_slot_templates |
bool | object | auto | No | true/unset = auto-detect (run if file exists), false = always skip, object = custom options |
spawn_data |
bool | object | always | No | true/unset = always runs (framework data embedded), false = disable entirely, object {file: …} = non-default mission file |
warehouses |
bool | object | auto | No | true/unset = auto-detect (run if src/warehouses.yaml exists), false = always skip, object = custom options |
weather |
bool | object | auto | No | true/unset = auto-detect (run if file exists), false = always skip, object = custom options |
When set to an object, the following sub-fields apply:
| Sub-field | Type | Default | Description |
|---|---|---|---|
file |
string | (see step defaults) | Path to the config file, relative to the mission folder |
mode |
add | replace |
add |
(group-injection steps only) add keeps existing groups; replace updates same-named groups |
enabled |
bool | true |
Disables the step without losing the other sub-fields — the long form of <step>: false |
kneeboards |
bool | true |
(presets step only) false keeps the frequency injection and renders no kneeboard PNG |
Auto-detection (when not set or true): the step runs only if its default file is found. Absence of the file silently skips the step.
Step 1 — Radio Presets (presets.yaml)¶
Injects radio frequency presets into every aircraft group that has at least one human pilot (Client/Player skill). Also generates kneeboard PNG images for each preset.
Disabling kneeboards: the step's mapping form accepts a
kneeboardssub-flag (defaulttrue). Settingpipeline: { presets: { enabled: true, kneeboards: false } }keeps the radio frequency injection but generates no kneeboard PNG plates (KNEEBOARD/<type>/IMAGES/presets[-<coalition>].png).
Default file location¶
<mission-folder>/src/presets.yaml
Two authoring formats¶
Since ADR 0010, presets.yaml accepts two layers, which coexist:
channel_lists(recommended): the mission-maker declares their channel lists once per coalition, by functional Radio role (primary UHF, primary VHF, FM…), and the build automatically projects each list onto every aircraft type's physical radios, honouring that type's hardware quirks (channel 0, reserved slots, hardcoded special channels, radio fusion — AJS-37, OH-58D, Mi-24P…). One frequency change propagates to the whole fleet. These per-type projection rules are documented on the developer side: Per-type radio-preset projection.radios_collection/presets_collection/presets_assignments(legacy): the mission-maker defines each preset's content radio by radio, then explicitly assigns it per aircraft type. This format remains fully supported and now serves as the manual override mechanism: an explicit assignment inpresets_assignmentsfor a given type always wins over thechannel_listsautomatic projection — including the special valuenone(no injection at all).
In both cases, channels_collection (the frequencies) stays the shared source for both formats.
convert-v5names frequencies automatically. When converting a v5 mission, hardcoded frequencies are replaced with names inpresets.yaml— the theatre's airfields (Gudauta,Batumi…) and VEAF call-signs (Guard,Archer,Texaco-1…) — and the matchingchannels_collectionis inserted to resolve them. A frequency with no known name is left as a raw number. The faithful copypresets.v5.yamlkeeps the raw frequencies.An airfield frequency is never typed. The
airports-<theatre>collections of the defaultpresets.yamlare generated from DCS (the frequencies the F10 view shows, and the TACAN): refer to an airfield by its name (02: Batumi). To choose the bases that deserve a channel and write a mission'sbasescollection with their real frequencies:content airfield-channels.
Schema — channel_lists (recommended model)¶
# ── Channel definitions (shared by both formats) ───────────────────────────
channels_collection:
<set-name>: # logical group of channels (e.g. airports-caucasus)
<channel-name>: # individual channel identifier
title: "Batumi / 16X" # human-readable label
freqs:
uhf: 260 # UHF frequency (MHz)
vhf: 131 # VHF-AM frequency (MHz)
fm: 40.4 # FM frequency (MHz)
# ── Channel lists by radio role ─────────────────────────────────────────────
channel_lists:
<coalition>: # blue | red
primary_1: # 1st V/UHF radio (uhf band)
01: Guard
02: Batumi
primary_2: # 2nd V/UHF radio (vhf band); also the warbirds' single radio
01: Guard
02: Batumi
fm_supplement: # FM as a 3rd radio, atop two primaries (e.g. A-10C)
01: 30
fm_substitute: # FM as the 2nd radio, replacing a 2nd primary (e.g. helicopters)
01: 30
fm_secondary: # a 2nd supplemental FM radio (e.g. OH-58D); defaults to a copy of fm_supplement
01: 31
Radio roles (fixed vocabulary):
| Role | Band | Usage |
|---|---|---|
primary_1 |
uhf | 1st V/UHF radio |
primary_2 |
vhf | 2nd V/UHF radio; the warbirds' single radio |
fm_substitute |
fm | FM replacing a 2nd primary radio (helicopters with a single primary radio) |
fm_supplement |
fm | FM atop two primary radios (attack aircraft, e.g. A-10C) |
fm_secondary |
fm | a 2nd supplemental FM radio (e.g. OH-58D); defaults to a copy of fm_supplement if not declared |
The build automatically assigns each aircraft type's physical radios to the role they match (deduced from their hardware frequency ranges), then projects that role's declared channel list onto it. A channel lacking a frequency for the role's band is silently dropped (reported under validate).
Channel 1 of the first radio also becomes the group's frequency: the flight starts on it. The shipped plan therefore puts no Guard on channel 1, nor anywhere: every aircraft reaches Guard with its own control, and a flight used to start on the distress frequency.
Schema — legacy format (manual override)¶
# ── Radio definitions ──────────────────────────────────────────────────────
radios_collection:
<set-name>: # logical group of radios (e.g. blue_radios)
<radio-name>: # identifier referenced in presets_collection
title: "UHF" # label shown on kneeboard
type: uhf # uhf | vhf | fm
channels:
01: Guard # channel number → channel-name (from channels_collection)
02: Batumi
10: Stennis
# ── Preset definitions ────────────────────────────────────────────────────
presets_collection:
<set-name>: # logical group of presets (e.g. blue_presets)
<preset-name>: # identifier referenced in presets_assignments
title: "Blue coalition - UHF/VHF/FM"
radios:
radio_1: <radio-name> # slot → radio-name (from radios_collection)
radio_2: <radio-name>
radio_3: <radio-name>
# ── Assignment rules (manual override of channel_lists) ────────────────────
presets_assignments:
<coalition>: # blue | red
<category>: # plane | helicopter
all: <preset-name> # default preset for all aircraft of this type
<aircraft-type>: <preset-name> # override for a specific DCS type (e.g. A-10C_2) or a regex pattern (e.g. A[-]10C.*)
<aircraft-type>: none # disables all injection for this type (no channel_lists equivalent)
Minimal example¶
channels_collection:
common:
Guard:
title: Guard
freqs:
uhf: 243.0
vhf: 121.5
channel_lists:
blue:
primary_1:
01: Guard
Channel value¶
A channel can be defined in three ways, in channel_lists as well as radios_collection:
- a channel name (alias resolved from
channels_collection):01: Guard; - a direct frequency (MHz):
01: 243.0; - an object:
01: { freq: 243.0, mod: 1 }, wheremodis the modulation (0= AM,1= FM). Themodfield is optional; when absent, DCS uses its default.
Channel priority and colour (kneeboards)¶
On a plan entry (object form), two optional attributes enrich the kneeboard (ADR 0012):
priority: <n>— highlights the channel on every kneeboard (aPnmarker + orange Name/Freq cells). On the AJS-37 (Viggen) only, priorities 1 to 4 additionally fill the FR22 Special 1/2/3 and FR24 H shortcut buttons. Declare it inchannel_lists(one entry per priority value).color: <colour>— colours the CH cell to group channels visually. Value: a colour name (green,blue…) or#RRGGBBAA. Accepted inchannel_listsand inchannels_collection(the plan entry wins over the channel definition).
channel_lists:
blue:
primary_1:
01: { channel: Guard, priority: 4, color: red } # Pn + highlight; on the Viggen → FR24 H
02: { channel: Texaco-1, priority: 1 } # on the Viggen → FR22 Special 1
03: { channel: Batumi, color: "#2E7D32" } # visual grouping (CH cell)
Kneeboards are generated one per injected aircraft type, in that type's DCS
folder (KNEEBOARD/<type>/IMAGES/).
v5 conversion: two files (convert-v5)¶
Since ADR 0010, convert-v5 emits two preset files:
presets.yaml— simplified plan (default, loaded by the build):channel_listsalone (plus the rare overrides the packer cannot project at all). The build projects the crystallisation onto every aircraft automatically — warbirds included (VHF/FM-capable radios), dropping out-of-band channels. This is the file that fully exploits the preset-plan model.presets.v5.yaml— faithful copy (reference / rollback, NOT loaded by the build): the complete iso-functional conversion (channel_lists+ a dedicated{coalition}_{aircraft}preset per bespoke layout, reproducing the exact channel → frequency map andmod, see ADR 0003).
Caution: the plan may make some frequencies diverge from the original v5 mission — warbirds move to the coalition channels, and jets' fused/modulated radios (F-14, AV8B…) are projected at best effort (partially) until a dedicated dcs-radio-layouts.yaml entry exists for their type. convert-v5 warns which aircraft are projected at best effort. Review and edit presets.yaml; when in doubt, the exact v5 reproduction stays in presets.v5.yaml (copy it over presets.yaml to restore iso-functional behaviour).
Frequency validation¶
At injection time, every frequency assigned to an aircraft is checked against the hardware specs of that aircraft's radios.
Default behaviour (normal build):
- Critical aircraft (
dcs_rejects_on_load: truein the specs): if a frequency is out of range, aWARNINGis emitted in the log. These are the aircraft for which DCS raises an error when the mission loads. - Other aircraft: validation is silent — DCS stores the frequencies but ignores them for out-of-range radios without crashing.
Automatic report:
After each preset injection, a file presets-validation-report.md is automatically created in the mission folder if at least one aircraft (critical or not) has out-of-range frequencies. The file lists all issues with the invalid values and a YAML snippet to disable them temporarily. If no issues are found, the file is deleted.
The report, and the build's log, also list every AI AWACS and tanker whose frequency is on no preset injected for its coalition: its pilots would find it neither on their radios nor on their kneeboard. It is the sign that the shipped channel plan was kept instead of the mission's; add its channel to src/presets.yaml. A coalition that received no preset is not checked.
<mission-folder>/presets-validation-report.md
The report commits without noise: it carries no date and no machine path. The presets file is named from the mission folder (src/presets.yaml) and the mission by its name, without the .miz's date; two builds of the same presets, from two folders or on two days, write the same file.
Temporarily disabling injection for an aircraft:
While fixing the presets, you can disable injection for a specific aircraft type by assigning it the value none in presets_assignments:
presets_assignments:
blue:
plane:
MiG-19P: none # DCS rejects standard frequencies — fix pending
Once the presets are corrected, remove the none line to re-enable injection.
Specs cover 100 player-flyable aircraft and are sourced from dcs-lua-datamine. If an aircraft is not in the database the check is silently skipped.
See also:
doc/mission-maker/dcs-radio-specs.md— full reference table of valid frequency ranges and list of critical aircraft.
To regenerate after a DCS update:poetry run update-radio-specs
Step 2 — Waypoints (waypoints.yaml)¶
Injects waypoint templates into human-piloted aircraft groups. Only groups with at least one Client/Player unit are modified.
Default file location¶
<mission-folder>/src/waypoints.yaml
Also accepted: waypoints.yaml (mission root)
Schema¶
# ── Waypoint definitions ───────────────────────────────────────────────────
waypoints:
<WAYPOINT_NAME>:
type: "Turning Point" # DCS waypoint type
action: "Turning Point" # DCS waypoint action
alt: 6096 # Altitude in metres
alt_type: "BARO" # BARO | RADIO
speed: 200 # Speed in m/s
speed_type: "TAS" # TAS | IAS
x: 75869 # Mission X coordinate
y: 48674 # Mission Y coordinate
name: "POTI" # What the cockpit shows (optional, the key when absent)
ETA: 364.89 # Estimated time of arrival in seconds (optional)
ETA_locked: false # Lock ETA (optional)
# ── Flight plan assignments ────────────────────────────────────────────────
settings:
<PLAN_NAME>:
category: "plane" # plane | helicopter (optional filter)
coalition: "blue" # blue | red (optional filter)
type: "F-16C_50" # DCS aircraft type (optional filter)
country: "USA" # Country name (optional filter)
waypoints: # the waypoints' KEYS, in flight order
- <WAYPOINT_NAME>
A plan takes its waypoints by key¶
A waypoint has a key (<WAYPOINT_NAME>) and a name:. A plan refers to it by its key; the cockpit shows its name:, or its key when it has none. Two waypoints may share a name but never a key: POTI at 10 000 ft for the planes and POTI_LOW at 500 ft above the ground for the helicopters, both shown as POTI.
A plan lists its waypoints as a list of keys. The older mapping form — HOLDING_POINT: "HOLDING_POINT" — still loads, but only its keys are read: the value is ignored, so POTI: "POTI_LOW" flies to POTI. The build warns when a value names another waypoint.
The build also appends a BULLSEYE waypoint, at the mission's own bullseye, to every flight plan: do not declare one.
Matching priority¶
Flight plans are matched to groups using this priority order:
1. Aircraft type (type)
2. Aircraft category (category)
3. Coalition (coalition)
4. Country (country)
5. All other groups (no filters = wildcard)
Minimal example¶
waypoints:
POTI:
type: "Turning Point"
action: "Turning Point"
alt: 3048
alt_type: "BARO"
speed: 180
speed_type: "TAS"
x: -295152
y: 617091
settings:
BLUE_PLANES:
category: "plane"
coalition: "blue"
waypoints:
- POTI
Step 3 — Aircraft Groups: spawnables (B) and dynamic-slot templates (C)¶
Two distinct uses of injected aircraft groups, handled by two independent steps (see ADR 0002):
- (B) spawnable groups (
src/spawnables.yaml, stepspawnable_aircrafts): real hidden groups cloned on demand in-game byveafSpawn. Marker: theveafSpawn-name prefix. - (C) dynamic-slot templates (
src/dynamic-slot-templates.yaml, stepdynamic_slot_templates): groups used as a model for DCS Dynamic Slots, consumed natively by the engine. Marker: the DCS flagdynSpawnTemplate = true.
At extraction (extract-aircraft-groups), each group is routed to one of the two families by this criterion (the flag wins over the prefix); other groups are ignored. By default extraction writes both files; --kind spawnable|dynamic-template restricts it to one. The old .*[tT]emplate.* name sort is abandoned (it misrouted a spawnable named "… Template …").
Default file locations¶
<mission-folder>/src/spawnables.yaml # (B) spawnable_aircrafts
<mission-folder>/src/dynamic-slot-templates.yaml # (C) dynamic_slot_templates
v6 hard break: the old
src/aircraft-templates.yaml/src/templates.yamlnames and theaircraft_groupsstep are gone.convert-v5produces the two new files directly.
Falling back to the shipped catalogue¶
These two steps are the only ones in the pipeline with a fallback: their input is a catalogue, most of which nobody edits. prepare therefore no longer lays down a copy — it writes an empty file — and resolution goes in this order:
- the mission folder's file, when it holds at least one group;
- otherwise the shipped catalogue, under
published/src/defaults/mission-folder/src/.
An absent or empty file means "I add nothing to the shipped catalogue", not "inject nothing". Two consequences worth knowing:
spawnable_aircrafts: false/dynamic_slot_templates: falseremains the only way to disable the step, and the fallback does not resurrect it;- an explicit
file:is taken at its word: when the named path does not exist the step is skipped rather than falling back to the shipped catalogue — a typo must not inject a catalogue nobody asked for.
When the fallback applies, the build report says so. To pull entries from the shipped catalogue selectively into a file that already has some, see pull-aircraft-groups.
Injection modes¶
| Mode | Behaviour |
|---|---|
add (default) |
Appends all groups from the file; existing groups are preserved |
replace |
Groups with the same name in the mission are updated; others are preserved |
Schema¶
airplanes:
coalitions:
<coalition>: # blue | red
<Country Name>: # DCS country name (e.g. "France", "USA")
<Group Name>:
name: "Group Name" # REQUIRED — must match the key
type: "M-2000C" # REQUIRED — primary DCS aircraft type
units: # REQUIRED — at least one unit
- type: "M-2000C"
name: "Pilot-1"
- type: "M-2000C"
name: "Pilot-2"
helicopters:
coalitions:
<coalition>:
<Country Name>:
<Group Name>:
name: "Group Name"
type: "UH-1H"
units:
- type: "UH-1H"
name: "Helo-1"
Minimal example¶
airplanes:
coalitions:
blue:
France:
Mirage-Flight:
name: "Mirage-Flight"
type: "M-2000C"
units:
- type: "M-2000C"
name: "Mirage-1"
- type: "M-2000C"
name: "Mirage-2"
Step 4 — Dynamic-Slot Warehouses (warehouses.yaml)¶
Configures DCS Dynamic Slots per coalition. It runs after aircraft
injection (so the dynSpawnTemplate groups already exist) and edits the
mission's warehouses: it enables dynamicSpawn on the selected airbases, sets
fuel / munitions and aircraft stock, and links each offered aircraft type to its
template group via linkDynTempl.
Default file location¶
src/warehouses.yaml (auto-enabled when present; disable with pipeline: { warehouses: false }).
Schema¶
<coalition>: # blue | red | neutral. An undeclared coalition is left untouched.
defaults: # applied to every selected airport
fuel: unlimited # optional -> unlimitedFuel
weapons: unlimited # optional -> unlimitedMunitions
aircrafts: # aircraft types offered as dynamic slots
<DCS type>: { amount: unlimited | <int>, template: "<group name>" }
airports: # optional. Absent -> ALL airports of this coalition get `defaults`.
<name or id>: { } # defaults only
<name or id>: { aircrafts: { ... } } # defaults + per-airport override
exclude_airports: # optional. These airports NEVER get a slot, listed or not.
- <name or id>
-
exclude_airportsis what the MCP actionset_airbase_coalition(…, dynamic_spawn: false)writes: without it, adynamicSpawn = falsein the mission'swarehouseswas turned back on at build for every base of a declared coalition with noairports:list (measured on GermanyCW-v6: 61 airports opened instead of 12). A base both listed and excluded stays closed, with a warning. -
templatereferences a template group by name; omit it to auto-match a template group of the same aircraft type (same coalition). - Airports may be named only on installed theatres present in the committed
airdrome table (
veaf-build update-dcs-data --airdromes); otherwise use the numeric id (visible in the mission'swarehousesasairports[<id>]).
Minimal example¶
blue:
defaults:
fuel: unlimited
aircrafts:
UH-1H: { amount: unlimited, template: "DST - UH-1H" }
airports:
Senaki-Kolkhi: {}
Where does a base's list of offered aircraft come from?¶
This is the most frequent question about dynamic slots, and the answer is written nowhere in
warehouses.yaml: the list is computed at build time.
A base offers the aircraft that have, in the mission, a dynamic-slot template group
(dynSpawnTemplate) of its own coalition. In practice:
- you place one group per aircraft type in the Mission Editor, ticked as a dynamic-slot template
(or you let step 3,
dynamic-slot-templates.yaml, do it for you); - at build time, the
warehousesstep collects those template groups per coalition; - every selected airbase gets all of its coalition's ones, stocked unlimited, plus a
linkDynTempllink to the matching template group.
That is why a three-line warehouses.yaml is enough to open the whole map: the list does not come
from the file, it comes from the template groups present in the mission. To add an aircraft, add its
template group; to remove one, remove the template group.
An explicit aircrafts: list (in defaults: or in an airport override) replaces that automatic
choice: only the types you list are stocked.
DCS only offers what the airfield can park¶
Even with the right stock and a linked template group, DCS only offers an aircraft when a parking spot able to take it exists. This is a terrain limit, not a tool limit: a field with nothing but helicopter pads will never offer a plane, whatever the stock says.
Measured on the Open Training Syria mission (2026-08-31), all of them blue bases with
dynamicSpawn on and the templates linked:
| Airfield | Parking spots | Offered |
|---|---|---|
| Akrotiri | 41 plane spots + 4 helipads | planes and helicopters (in game) |
| Lakatamia | 8 helipads only | helicopters only (in game) |
| Naqoura | 9 helipads only (no runway) | helicopters only (in game) |
| Taftanaz | 48 helipads only | helicopters only (from the parking data) |
What the tool does. It asks the terrain before filling: an airbase is stocked only with the
categories it can park, and stock that has become unplayable (written by an earlier build) is
removed. This applies to an explicit aircrafts: list too — DCS would ignore what it cannot park
anyway. The filtering is silent: no build warning.
Reach. Parking data ships for every map but Iraq, Kola and Nevada. On those three theatres — and on an airfield absent from the data — nothing is filtered and the behaviour is unchanged.
How to check it in game. Start the mission, pick the side, open the slot picker on the airfield in question: the list shown is what DCS can actually spawn. If a plane is missing on a field that only has helipads, that is the expected behaviour; the fix is to pick another airfield, not to edit the stock.
Step 5 — Spawn Data (spawn-groups.yaml)¶
The _spawn unit <alias> and _spawn group <alias> marker commands rely on two Lua tables (veafUnits.UnitsDatabase and veafUnits.GroupsDatabase). Since v6 these are no longer hard-coded in veafUnits.lua: they come from YAML, are rendered to Lua, and injected into the .miz at mission build (DCS cannot parse YAML at runtime). See ADR 0005.
Always on¶
Unlike the other steps, spawn_data always runs (even with no mission file) because the framework spawn database must be embedded for _spawn to work. To disable it entirely:
pipeline:
spawn_data: false
Extending the database (src/spawn-groups.yaml)¶
An optional src/spawn-groups.yaml lets a mission add or override units/groups. It is merged over the framework data:
- a brand-new alias is appended;
- an alias already present in the framework replaces that entry (override).
Schema¶
units: # -> _spawn unit <alias>
- aliases: [myaaa] # one or more case-insensitive aliases
unitType: ZSU-23-4 Shilka # a DCS unit type id
groups: # -> _spawn group <alias>
- aliases: [mysam]
disposition: {h: 3, w: 3} # placement grid in cells (10m x 10m)
units:
- {type: ZSU-23-4 Shilka, cell: 1}
- {type: Ural-375, random: true} # randomized within its cell
- {type: Soldier M4, number: {min: 2, max: 4}, random: true}
description: My custom SAM site
groupName: MySAM
Group-unit fields: type (required), cell (preferred cell), number (count, or {min, max} random), hdg (heading), size (fixed cell size in m), random (randomize within the cell), fitToUnit (cell shrunk to the unit's exact footprint).
The framework database lives in veaf_libs/data/veaf-units.yaml (bundled with the tool).
Step 6 — Weather & Time Versions (versions.yaml)¶
Creates multiple .miz variants from a single base mission, each with a different time and/or weather configuration.
Default file location¶
<mission-folder>/src/versions.yaml
Also accepted: versions.yaml (mission root)
Schema¶
# ── Geographic position for solar calculations (optional) ─────────────────
# Absent: one point per theatre, on the mission's map (near Batumi on the Caucasus…).
position:
latitude: 33.5 # Decimal degrees, -90 to 90
longitude: 35.5 # Decimal degrees, -180 to 180
timezone: "Asia/Damascus" # IANA zone, fallback for an unknown theatre (else: the theatre's DCS clock)
# ── Base date for all versions ─────────────────────────────────────────────
base_date: "2024-03-15" # ISO 8601 (YYYY-MM-DD)
# ── Mission variants ───────────────────────────────────────────────────────
versions:
- name: dawn # REQUIRED — variant name (output filename suffix)
time: "sunrise+30*60" # Time expression (see below)
date: "today" # Date expression (see below, optional)
metar: "METAR OSDI 151420Z 27015G25KT 9999 SKC 15/10 Q1018" # optional
- name: noon
time: "12:00"
weather: # manual weather (alternative to metar)
temperature: 25.0 # °C, range -50..50
wind_speed: 8.0 # m/s, wanted 10 m above the reference airfield
wind_direction: 270.0 # degrees (0=North)
visibility: 9999 # metres
cloud_type: "clear" # clear | few | scattered | broken | overcast
cloud_height: 2000 # cloud base in metres
fog_enabled: false # enable fog effect
versions[] fields¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Variant name. Inside mission build, the output is missions/<BaseName>_<name>.miz (e.g. dawn → My-Mission_dawn.miz); the bare <name>.miz form only exists with standalone inject-weather |
time |
string | No | Time expression — see below |
date |
string | No | Date expression — see below |
metar |
string | No | Full METAR string — parsed for the weather data, and showable in the briefing through ${METAR} |
airport_icao |
string | No | ICAO code whose live weather is fetched (used without metar) |
weather |
object | No | Manual weather override (used without metar or airport_icao) |
clearsky |
boolean | No | Caps the weather to visual-flight conditions: clouds at most FEW, a simulated surface wind under 15 kt at the reference airfield, no gusts, visibility 10 km or more, no rain, no fog. Keeps an airport_icao's real weather flyable without instruments; the briefing's ${METAR} describes the capped sky. Default false |
Showing the weather in the briefing: ${METAR}¶
Write ${METAR} in the mission briefing (in the DCS editor: Situation, or one of the per-coalition
tasks) and the build replaces it with this variant's weather. Seven weather variants therefore give
seven different briefings, with nothing retyped.
Situation: reconnaissance flight over the north of the theatre.
Weather at departure: ${METAR}
This was the point of #40: a mission is rebuilt from its sources on every build, so anything typed into the briefing by hand is overwritten next time. Substituting during the build is what fixes that.
What ${METAR} resolves to, per variant:
| The variant declares | ${METAR} shows |
|---|---|
metar: "..." |
the string exactly as you wrote it |
airport_icao: LFRS |
the live METAR fetched for that station |
metar: or airport_icao:, with clearsky: true |
a METAR recomposed from the capped weather the mission flies, built the same way as for weather: below but keeping the report's station, its temperature and its QNH — METAR LTAG 150203Z 35006KT 9999 FEW090 19/// Q1015. Always recomposed, even when the caps change nothing: a TEMPO TSRA left in the published text would announce a storm the sky never has |
only weather: |
a METAR composed from it: wind, visibility, rain and fog, cover and base (the one DCS flies, which moves the base into its cloud preset's range), temperature (no dew point: written ///), the mission's QNH, stamped with the variant's date and time in UTC and no station — METAR 150900Z 27010KT 9999 SKC Q1013. With clearsky: true, the values are the capped ones |
| no weather at all | nothing — there is no METAR to show, so the text ${METAR} stays as written and a warning says so in the log |
A published METAR keeps its own time
A metar: written in the file or one fetched for an airport_icao is shown as published, time
group included: a report fetched at 18:20Z on the 2nd reads 021820Z in a variant that starts at
dawn on another date. Only a composed or recomposed METAR (weather:, or clearsky: true) is stamped
with the variant's date and time.
An unknown ${...} is never blanked
${METRA} stays written as ${METRA} in the briefing. Deliberately: a briefing is read by players,
and a hole where the text should be reads as the build having eaten your prose, while a visibly
misspelt name tells you what to fix.
No network call if you do not use it
The ICAO METAR fetch only happens when the briefing actually contains ${METAR}.
Time expressions¶
| Format | Example | Meaning |
|---|---|---|
HH:MM |
14:30 |
Absolute time (14:30 local) |
sunrise |
sunrise |
Sunrise time (at position:, else at the theatre's point) |
sunset |
sunset |
Sunset time (at position:, else at the theatre's point) |
| Expression | sunrise+30*60 |
Arithmetic — seconds offset |
| Seconds | 54000 |
Raw seconds since midnight |
Date expressions¶
| Format | Example | Meaning |
|---|---|---|
| ISO 8601 | 2024-03-15 |
Specific date |
| Keyword | today, tomorrow, yesterday |
Relative to run date |
| Relative | +1, -7 |
Days from base_date |
weather object fields¶
| Field | Type | Default | Description |
|---|---|---|---|
temperature |
number | — | Air temperature in °C |
wind_speed |
number | — | Wind wanted 10 m above the reference airfield, in m/s — not the editor's setting, which DCS multiplies by up to 2.1: the build writes the setting that gives it (the wind) |
wind_direction |
number | — | Direction the wind comes from, in degrees (0 = North), as in a METAR |
visibility |
number | — | Visibility in metres; 9,000 m or more is written as DCS's maximum, 80 km — a METAR's 9999, "10 km or more" |
cloud_type |
string | — | clear | few | scattered | broken | overcast |
cloud_height |
number | — | Cloud base altitude in metres |
fog_enabled |
boolean | false |
Enable fog effect |
precipitation |
boolean | false |
Rain: picks one of DCS's rainy cloud presets (RainyPreset…) |
reference_airfield |
string | — | Airfield the wind is wanted at (DCS name); the METAR's station on the map wins, the first player group's departure is the default, sea level the last resort |
wind_gust |
number | — | Gust in m/s, made into DCS turbulence (gap over the mean / 0.62, capped at 3 m/s) |
turbulence |
number | — | Turbulence in m/s, as the editor shows it; wins over wind_gust |
wind_veer |
number | 20 |
Turn of the wind to the right from the surface to 2,000 m, 0 to 30° |
wind_speed_2000 / wind_direction_2000 |
number | — | 2,000 m wind set by hand (m/s, where it comes from); default: 2.2 times the ground setting, veered wind_veer |
wind_speed_8000 / wind_direction_8000 |
number | — | 8,000 m wind set by hand; default: the 2,000 m one plus 3 m/s |
Coverage and base pick a DCS cloud preset, the only thing DCS renders since 2.7: the first preset
of that coverage whose altitude range holds the base, otherwise the base is moved into the nearest
one's range (ranges read from DCS's Config/Effects/clouds.lua). A METAR also brings the pressure
(Q1018, A2992), rain (RA, DZ, TS...) and fog (FG).
Minimal example¶
position:
latitude: 42.3
longitude: 43.4
timezone: "Asia/Tbilisi"
base_date: "2024-06-01"
versions:
- name: dawn
time: "sunrise+20*60"
metar: "METAR UGTB 010500Z 27010KT 9999 FEW030 18/10 Q1016"
- name: afternoon
time: "15:00"
weather:
temperature: 28.0
wind_speed: 5.0
wind_direction: 180.0
cloud_type: "few"
cloud_height: 2500
See Also¶
- mission.yaml Reference — top-level mission configuration
- Mission Maker Guide — complete workflow