Skip to content

mission.yaml Reference

mission.yaml is the optional build-time configuration file for veaf-tools. Place it at the root of your mission folder, next to veaf-tools-updater.exe. If absent, veaf-tools mission build works with default settings.

This page covers the top-level sections of mission.yaml. Configuration for individual Lua modules is documented in each module's own page (see the index by module below).


Understanding the YAML file landscape

A VEAF mission folder uses two distinct categories of YAML files. Understanding the difference helps you know which file to edit for a given task.

Category A — Build pipeline files

These files drive the design-time injection steps that veaf-tools mission build performs before writing the final .miz. Each step reads its own YAML file and injects data into the mission. They are listed under the pipeline: section of mission.yaml.

File (in src/) Pipeline step What it does
waypoints.yaml waypoints Injects named waypoints into the mission
presets.yaml presets Configures radio presets for each aircraft group
spawnables.yaml spawnable_aircrafts Spawnable aircraft groups (veafSpawn- prefix)
dynamic-slot-templates.yaml dynamic_slot_templates Dynamic-slot templates (dynSpawnTemplate=true)
warehouses.yaml warehouses Dynamic-Slot warehouses: dynamicSpawn, stock, fuel, template links
spawn-groups.yaml spawn_data Spawn database for _spawn unit / _spawn group — optional: the step always runs, the framework data being embedded, and this file only adds to it
versions.yaml weather Generates one .miz variant per weather preset

These files are not loaded at DCS runtime — they are consumed by veaf-tools mission build and then compiled into the .miz.

Category B — Runtime module configuration (this file)

mission.yaml itself configures how VEAF Lua modules behave at DCS runtime. It is translated at build time into veaf-config.lua, which is injected into the mission and executed when DCS loads the mission.

The modules: section describes runtime module behaviour (QRA, assets or shortcuts configuration lives under the relevant module, e.g. modules.QRA).

mission folder/
├── mission.yaml          ← runtime module config (THIS FILE)
│   └── pipeline:
│       ├── waypoints: true  ──► src/waypoints.yaml     (build-time injection)
│       ├── presets:  true   ──► src/presets.yaml        (build-time injection)
│       ├── spawnable_aircrafts: true     ──► src/spawnables.yaml
│       ├── dynamic_slot_templates: true  ──► src/dynamic-slot-templates.yaml
│       ├── warehouses: true  ──► src/warehouses.yaml
│       ├── spawn_data: true  ──► src/spawn-groups.yaml  (optional)
│       └── weather:  true   ──► src/versions.yaml
└── src/
    ├── waypoints.yaml
    ├── presets.yaml
    ├── spawnables.yaml
    ├── dynamic-slot-templates.yaml
    ├── warehouses.yaml
    ├── spawn-groups.yaml
    └── versions.yaml

Minimal working example

# mission.yaml — minimum viable configuration
global_log_level: debug           # remove before deploying to players

mission:
  name: "My-Mission"

security:
  disabled: true

modules:
  RADIO:
    enabled: true
  ASSETS:
    enabled: true
    assets:
      - sort: 1
        name: "Texaco"
        description: "Texaco (KC-135)"
        information: 'Tacan 51Y\nU251.00 (21)'

Syntax errors

If mission.yaml contains a YAML syntax error (wrong indentation, a missing colon, a tab character…), veaf-tools mission build stops immediately and displays a clear message indicating the file name, the line and column of the problem, and a plain-language hint on how to fix it:

Syntax error in mission.yaml, line 81, column 4.
  The error starts near line 34, column 3.
  → Check the indentation around these lines. YAML uses spaces only (never tabs).
    All items in the same block must be aligned at the same column.

Common causes:

Symptom Cause Fix
indentation hint A block is indented differently from the rest Align the key with its siblings
tab hint A tab character was used instead of spaces Replace all tabs with spaces in your editor
colon hint A key is missing its : separator Write key: value, not key value

Tip

Most text editors can visualise whitespace characters — enable this option to quickly spot tab/space mix-ups.


Top-level sections

global_log_level

Forces a log level on all VEAF modules. Remove before deploying to players.

global_log_level: debug     # error | warning | info | debug | trace
Value Description
error Errors only
warning Errors and warnings
info Standard operational messages (production default)
debug Detailed module activity — use during development
trace Extremely verbose — use when tracking specific issues

mission:

Mission identity (name, export path, era) and mission-wide options (e.g. silence_atc_on_all_airbases) — used in radio menus, log messages, and export paths. This block holds mission-level settings, not just identity.

mission:
  name: "My-Mission"          # shown in radio menus and log messages
  export_path: null           # null = default DCS Saved Games path
  era: MODERN                 # MODERN | COLD_WAR | WW2
  language: fr                # in-game VEAF message language: fr | en (default: the tools' language)
  silence_atc_on_all_airbases: false  # mission-wide option: mute DCS ATC at every airbase
  third_party_mods: []        # third-party DCS mods to make non-blocking (see below)
  ground_control:             # game master, JTAC… slots and vehicle control (see below)
    game_master: 5
  forced_options:             # options enforced on every player (see below)
    labels: none
Field Type Default Required Description
name string — No Mission name shown in menus and logs, and the name of the built .miz — see the naming note below
export_path string | null null No Override DCS Saved Games export path
era string inferred No MODERN | COLD_WAR | WW2 — affects available spawn groups. When absent it is inferred at every build from the base mission's content: a WW2 unit type or a year ≤ 1945 gives WW2, a year ≤ 1991 COLD_WAR, otherwise MODERN (era_detector.py). The inferred value is not written back into your mission.yaml — it is recomputed; set the key to pin it.
silence_atc_on_all_airbases boolean false No Mission-wide option: mute DCS ATC at every airbase (emits veaf.silenceAtcOnAllAirbases()). convert-v5 migrates it from an active call and annotates its provenance
language string tools' language No Language of in-game VEAF messages (fr | en); emitted into veaf-config.lua as veaf.config.language and read by veaf.t(). When omitted, the build uses the tools' language (--lang > VEAF_LANG > user config > OS locale > en)
third_party_mods list of strings [] No Third-party DCS mods (paid/community aircraft) to make non-blocking: their ids are removed from the .miz's requiredModules table at build, so a pilot who does not own the mod can still load the mission (that slot is simply unavailable). The list is unioned with a VEAF default list covering the common mods (Hercules, UH-60L, A-4E-C, T-45, AM2, SU-30/FlankerEx, Bronco-OV-10A) — only declare mods not already handled. Not to be confused with VEAF Modules (the modules: block, which are capabilities, not DCS add-ons)
ground_control mapping absent No Slots of the roles that fly no aircraft, and vehicle control — see below
forced_options mapping absent No Options enforced on every player (labels, F10 view…) — see below

The .miz file name is an interface — _ICAO_<code> and real weather

mission.name becomes the built file name: <name>_<YYYYMMDD>.miz, plus a _<VARIANT> suffix when build_variants: is used. Give a name ending in .miz instead and the name is taken verbatim, with no date.

That matters because server-side tooling reads the file name. On the VEAF servers, the RealWeather extension of DCSServerBot looks for _ICAO_<code> in the mission's file name and fetches that airfield's live METAR at mission start — so the weather follows reality without rebuilding. Name the mission accordingly:

mission:
  name: VEAF_Foothold_Caucasus_ICAO_URSS   # -> VEAF_Foothold_Caucasus_ICAO_URSS_20260728.miz

Two rules when picking the code:

  • it must be an airfield on that theatre (any one; a large one is better);
  • it must have a live METAR station. Check before trusting it — a station can exist and be stale, which is worse than no real weather at all, since the mission then advertises a "real" weather that is days old. A one-line check:
curl -s https://tgftp.nws.noaa.gov/data/observations/metar/stations/URSS.TXT

The first two digits of the DDHHMMZ group are the day of observation — compare them with today.

When a whole theatre is degraded, there are two defensible answers, and the choice belongs to whoever runs the server. Measured on Afghanistan, every station lags (Kabul a month, Herat sixteen days, Bagram a day; six airfields have no station at all): either omit the marker, so RealWeather leaves the mission alone and the authored weather stands, or take the least bad and know what you are getting. The VEAF Foothold Afghanistan uses OAIX (Bagram, about a day behind) — a deliberate choice, not an oversight.

ground_control: game master, JTAC, observer slots, and vehicle control

DCS keeps in the mission how many players may take each role that does not fly, on each side, and whether a pilot may drive ground vehicles. A v6 mission is born with none of these slots: no game master until they are opened. The block sets them at every build:

mission:
  ground_control:
    game_master: 5             # "Game Master" (instructor in the mission)
    tactical_commander: 5      # "Tactical Commander" (artillery_commander)
    jtac: {blue: 5, red: 0}    # "JTAC/Operator" (forward_observer), per side
    observer: 5                # "Observer"
    pilot_control_vehicles: true  # a pilot may take control of ground vehicles
  • A number applies to both sides; {blue: n, red: m} sets them apart.
  • A role, a side or pilot_control_vehicles the block does not name keeps the mission's value.
  • Block absent: the build changes nothing, a mission set up in the editor keeps its slots.
  • An unknown role or side, a negative number: validate reports it and the build stops.
  • Role passwords stay out of reach: written in mission.yaml, they would be committed in clear.

The VEAF Open Training missions open 5 slots per role and per side, with vehicle control, as in v5.

forced_options: the options enforced on every player

The editor's Mission options panel enforces some options on every player, whatever their own settings: labels, F10 map view, external views, wake turbulence… The block sets them at every build, in the mission's forcedOptions table:

mission:
  forced_options:
    labels: none               # full | abbreviated | symbol_only | dot_neutral | none
    f10_view: fog_of_war       # all | fog_of_war | allies_only | map_only | my_aircraft
    external_views: false
    f10_marks: true            # players' F10 marks (the VEAF marker commands need them)
    wake_turbulence: true
    civil_traffic: off         # off | low | medium | high
Name Editor label Values
labels Labels full, abbreviated, symbol_only, dot_neutral, none
spotting_dot Spotting dot off, auto, one_pixel, two_pixels
f10_view F10 View Options all, fog_of_war, allies_only, map_only, my_aircraft
f10_view_by_role F10 View Options → Extended see below
f10_marks F10 Map User Marks true / false
external_views External views true / false
padlock Padlock true / false
permit_crash Permit crash recovery true / false
easy_flight Easy flight true / false
easy_communication Easy communication true / false
radio_assist Radio assist true / false
mini_hud Mini HUD true / false
cockpit_visual_recon_mode Cockpit visual recon mode true / false
cockpit_status_bar Cockpit Status Bar true / false
battle_damage_assessment Battle Damage Assessment true / false
unlimited_fuel Unlimited fuel true / false
unlimited_weapons Unlimited weapons true / false
immortal Immortal true / false
g_effects G-effect none, reduced, realistic
g_warm_up G warm up in air true / false
wake_turbulence Wake turbulence true / false
unrestricted_satnav Unrestricted SATNAV true / false
birds Birds a number from 0 to 1000
civil_traffic Civ traffic off, low, medium, high
random_failures Random System Failures true / false
  • An option the block does not name keeps the mission's value; the block never removes one. To stop enforcing an option, untick it in the editor.
  • Block absent: the build changes nothing, a mission set up in the editor keeps its options.
  • An unknown option or value: validate reports it and the build stops.
  • DCS does not apply enforced options to a player who ticked the global preset of the GAMEPLAY tab of their options (the editor panel's note).
  • In a campaign, put the block in the mission.yaml of the template/ folder: campaign next copies it into every mission.

The F10 view per role. f10_view_by_role replaces f10_view (both together are refused) and sets each role's map:

mission:
  forced_options:
    f10_view_by_role:
      pilot: {view: fog_of_war, hide: [enemies_weapons]}
      jtac: all
      spectator: all
  • Roles: pilot, jtac, tactical_commander, observer, spectator, airboss; a role not named sees everything.
  • A view alone, or {view: …, hide: […]} with the layers to hide: <side>_<category>, side allies, enemies or neutral, category planes, helicopters, ground, naval or weapons.
  • What DCS does with it in game is not seen yet: the shape comes from the editor's code, no saved mission used it.

security:

Controls the VEAF security system. Security is active by default (veaf.SecurityDisabled = false in veaf.lua): with no security: block nothing is emitted and the sensitive commands require a pilot level or a password. disabled: true turns it off for the whole mission.

security:
  disabled: false                   # false = a password is required
  password_hashes:                  # SHA-1 hashes granting player/JTF access
    - "2a4efd2397e081bcacb82b3e447c584c65cc83ee"
  password_mm_hashes:               # SHA-1 hashes granting Mission Master access
    - "99685b3c7cb1fb08a829fc97d4a8564fc5f9435a"
Field Type Default Required Description
disabled boolean false No true = no password required
password_hashes string[] [] No SHA-1 hashes granting player access. Emitted at levels L1 and L9, so the password opens marker authentication and the sensitive spawns, not only the L9 gates
password_mm_hashes string[] [] No SHA-1 hashes granting Mission Master access (its own table, no level cascade)

SHA-1, not SHA-256. veafSecurity._checkPassword hashes what the player types with sha1.hex(password) and looks it up in the table, so a SHA-256 hash never matches and the password silently never works.

To generate one: echo -n "yourpassword" | sha1sum (Linux/macOS), or python -c "import hashlib,sys; print(hashlib.sha1(sys.argv[1].encode()).hexdigest())" yourpassword.


settings:

Arbitrary key-value pairs injected into the mission as veaf.config.KEY = value. Use this to pass mission-specific constants to Lua scripts.

settings:
  MY_MISSION_FLAG: 42
  ENABLE_CONVOY: true
  MISSION_PHASE: "alpha"

Each key becomes veaf.config.MY_MISSION_FLAG = 42 in the generated veaf-config.lua.


module_settings:

Scalar settings written straight onto a VEAF module's table in the generated veaf-config.lua. Where settings: can only write veaf.config.KEY, this section targets any VEAF table.

module_settings:
  veafSkynet.DelayForStartup: 150     # IADS start-up delay
  veafSkynet.DynamicSpawn: true
  veafRadio.RadioMenuName: "BFR"      # root radio menu name, seen by players
  veaf.DEFAULT_GROUND_SPEED_KPH: 25

Each entry becomes the matching Lua assignment, here veafSkynet.DelayForStartup = 150.

Field Type Default Required Description
(key) string — — The full Lua target, veafXxx.Field. A key that does not start with veaf is refused at generation time: this section is a migration path for VEAF settings, not a hatch for writing anywhere in the runtime
(value) boolean | number | string — — A scalar. Tables and functions cannot be expressed here

A documented field beats a migration leftover. When a setting now has its own field in mission.yaml and a module_settings: entry targets the same Lua variable, the documented field is what applies: the module_settings: entry is dropped, and the build says so, naming both. This section is a migration path, not a permanent override — delete the line that is no longer needed.

One setting is in that position today: veaf.HideNamesFromSpawnedGroups, superseded by mission.hide_names_from_spawned_groups.

Where it comes from. convert-v5 fills this section automatically: under v5, half of these settings reached neither mission.yaml nor the generated Lua, and nothing reported it. Whatever the conversion still cannot carry — a table, a function — is now listed as comments in the generated mission-script.lua, under "Settings NOT migrated".


Third-party modules: SKYNET / CTLD / CSAR (under modules:)

v6 change (hard break): the external_modules: and qra: sections no longer exist. All of their configuration now lives under the modules: block, the single source of truth. See ADR 0001.

Skynet IADS, CTLD and CSAR are configured like any other module, directly under modules::

modules:
  SKYNET:
    enabled: true
    include_red_in_radio: false      # add RED IADS status to F10 menu
    debug_red: false                 # verbose Skynet debug for RED
    include_blue_in_radio: false     # add BLUE IADS status to F10 menu
    debug_blue: false                # verbose Skynet debug for BLUE
  CTLD: true                         # configured in ctld-config.yaml, not here
  CSAR:
    enabled: true
    settings:                        # csar.xxx = value pairs
      enableAllslots: true

modules.SKYNET fields

Field Type Default Description
enabled boolean false Enable Skynet IADS integration
include_red_in_radio boolean false Add RED IADS status to F10 radio menu
debug_red boolean false Enable verbose Skynet debug for RED coalition
include_blue_in_radio boolean false Add BLUE IADS status to F10 radio menu
debug_blue boolean false Enable verbose Skynet debug for BLUE coalition
spotter_network boolean false Spotter network: ground units see aircraft and relay the contact
spotter_radio_range_km number 20 How far one unit can relay, in kilometres
spotter_propagation_speed_kmh number 3600 How fast an alert crosses the network, in km/h (hop period = range / speed)
spotter_view "off" | "on" | "radio" "off" F10 map view of the spotter network; "radio" puts the switch in the F10 menu. Quote it: YAML reads a bare on/off as a boolean

modules.CSAR fields

Field Type Default Description
enabled boolean false Enable CSAR integration
settings mapping — csar.xxx pairs (e.g. enableAllslots: true)

VEAF generates the csar.xxx = value assignments and the csar.initialize() call in veaf-config.lua. On convert-v5, these settings are extracted automatically from missionConfig.lua. For complex settings such as aircraftType (a per-aircraft table), continue using the Lua callback pattern in mission-script.lua.

modules.CTLD: a switch, a few VEAF keys, no settings:

CTLD 2 is configured outside mission.yaml, in a ctld-config.yaml file sitting next to it and edited with ctld-tools.exe. A settings: block under CTLD is rejected by validate: it was no longer read, and letting it pass in silence is exactly the defect this change removes. Besides the switch, mission.yaml only carries keys read by VEAF at runtime, never by the CTLD engine:

  • manage_logistics (boolean, default true) — with it on, the build adds the carriers and FARP ammo dumps VEAF has always recognised to the logisticUnitTypes / troopZoneShipTypes lists in your ctld-config.yaml; it adds, it does not replace.
  • manage_airbase_logistics (boolean, default true) — VEAF registers every airfield on the map as a CTLD logistic zone and keeps it up to date (two classes, two minutes of ground occupation for a neutral or captured field). At false, nothing is registered and the log says the feature was explicitly switched off.
  • airbase_logistics_radius (number greater than 0, default 250) — radius, in metres, of the logistic zone and of the green circle drawn around each airfield. One value for every field: size it for the largest airfield the transports start from (1,100 m for a major air base such as Ramstein, measured 2026-10-03).
  • airbase_occupation_radius (number greater than 0, default 2000) — radius, in metres, probed around the airfield for ground occupation.
  • airbase_logistics_tick (number greater than 0, default 30) — re-evaluation period, in seconds.
modules:
  CTLD:
    enabled: true
    manage_logistics: true
    manage_airbase_logistics: true   # default
    # airbase_logistics_radius: 250
    # airbase_occupation_radius: 2000
    # airbase_logistics_tick: 30

See CTLD and CSAR Integration — including where to get ctld-tools, which does not ship with VEAF MCT, FARPs placed in the editor, and airfields become logistic zones.

Sounds. CTLD and CSAR play sounds by filename at runtime (beacon.ogg, beaconsilent.ogg, CSAR.ogg). When CTLD or CSAR is enabled, the build automatically injects the required sounds it ships (src/scripts/community/sounds/) into the mission's l10n/DEFAULT/, without overwriting any sound your mission already provides. A required sound shipped by neither the tools nor your mission is reported with a build warning — add it to src/mission/l10n/DEFAULT/ (e.g. radiobeep.ogg, the JTAC fallback beep, is not redistributed).

The build then declares every sound the mission carries in mapResource, through a "Declare mission sounds" trigger that plays it to a country no coalition uses — so nothing is audible. Without that declaration the DCS Mission Editor treats these files as orphans (it cannot know a script plays them by name) and deletes them the moment you save the mission in the editor, with no message at all.


veaf_tools:

Version compatibility constraint for veaf-tools-updater.exe. The updater skips releases that don't match.

veaf_tools:
  version: "6"          # accept any 6.x.x
Format Example Meaning
Major only "6" Any 6.x.x
Major.Minor "6.1" Any 6.1.x
Exact "6.1.3" Exactly 6.1.3
Compatible "^6.1.3" >=6.1.3, <7.0.0
Approximate "~6.1.3" >=6.1.3, <6.2.0

modules:

The unified modules: block enables, disables, or configures every VEAF Lua module and every community script in a single place. Modules not listed are enabled with their default settings.

Migration note. modules: replaces the older lua_modules: + community_scripts: keys. The legacy keys still work but emit a deprecation warning at build time. enabled: replaces the old enable: key.

Each entry can take three forms:

modules:
  # 1. Shorthand — just enable an optional module with default settings:
  RADIO: true

  # 2. Block — enable and configure:
  SPAWN:
    enabled: true
    logLevel: debug           # optional per-module log level override
    init:
      help_menus: true

  # 3. Bare null — mandatory infrastructure module, configure only:
  UNITS:
    logLevel: debug

Infrastructure modules are always active. UNITS, TIME, CACHE, EVENTS, MARKERS, and COMMANDS are mandatory and always loaded. Do not set enabled: on them — it is a build error. They may still appear in modules: to configure other fields such as logLevel.

Common fields (all modules):

Field Type Default Description
enabled boolean true Enable or disable this module (optional modules only — not allowed on Infrastructure modules)
logLevel string (global) Override log level for this module only
initialized_by_mission boolean false true: the module's script is loaded and its configuration applied, but the build does not call its initialize() — your own scripts do. For a mission that starts a module itself (otherwise it is initialized twice); also accepted under SKYNET, CTLD and CSAR, for which false would drop the script. See Porting a mission with its own script layer

Additional init: or data fields are module-specific — see each module's documentation page.

RADIO init: fields:

Field Type Default Description
help_menus boolean false true shows the built-in "Help" entries of the radio menus; negated into veafRadio.initialize's skipHelpMenus
create_menus boolean true false builds no VEAF F10 menu at all (dontCreateMenus). Combined with security:, that is how a public mission keeps the VEAF commands reachable only through password-protected map markers. Omit the key to keep today's behaviour
modules:
  RADIO:
    enabled: true
    init:
      create_menus: false       # no VEAF radio menu; commands via markers only

CARRIER init: fields:

Field Type Default Description
include_carrier_operations_radio boolean true false: no carrier menu in F10
operations_scheduler boolean true false: no VEAF operations loop (every 60 s), for a mission that drives its carriers — see veafCarrierOperations

RADIO diagnostic option (outside init:): menu_stats: true writes to dcs.log, on every refresh, the F10 menu's size and what changed. Turn it on temporarily in a real mission to watch the menu in multiplayer — see Watching the F10 menu.

Community scripts are listed in the same block, using their IDs (case does not matter: CTLD: and ctld: are equivalent). When a script is absent from modules:, it keeps its default state — included for the opt-out scripts, excluded for the two opt-in ones (MIST and TUM, see below). Set a script to false to exclude it:

Because that default is easy to misread, generated files (prepare --template, convert-v5) and the shipped mission.yaml always write the five opt-out scripts (STTS, CTLD, AIEN, CSAR, SKYNET) out, at true or false: the state of a community script is never inferred from silence.

modules:
  CTLD: true
  CSAR: true
  SKYNET: false               # excluded from this mission
Community ID Script
MIST MIST (Mission Scripting Tools) — opt-in, and auto-enabled when one of your own scripts calls mist.
STTS DCS-SimpleTextToSpeech
CTLD CTLD (Combat Transport & Logistics Dispatcher)
AIEN AIEN (AI Enhancement)
CSAR CSAR (Combat Search and Rescue)
SKYNET Skynet IADS
TUM The Universal Mission (TUM)

An unknown identifier in modules: is a blocking error: the build stops with a message naming the offending key.

MIST — opt-in, and injected only when something needs it. MiST used to be injected into every mission, because the VEAF scripts called it everywhere. They no longer call it at all, so it is now off by default and the 336 KB it weighs stay out of your .miz. Before packaging, the build scans your own src/scripts/*.lua; if one of them calls mist., MiST is injected and the build log names the file that asked for it. A MIST: false does not win against that scan — a script that calls MiST gets MiST, otherwise it would break in flight. Set MIST: true only when something reaches MiST indirectly, which the scan cannot see (through _G["mist"], or from a script loaded by another script). A bare MIST: line inherited from an older mission.yaml now reads as "disabled", which is the right answer for almost every mission. See MiST: injected only when you need it.

TUM (The Universal Mission) — mission prerequisite. TUM is a self-contained PvE mission generator (third-party community script) that takes over the whole map at start-up: it makes every airbase neutral, then assigns zones and airfields to the coalitions based on the trigger zones defined in the mission editor. If you enable TUM: true on a mission that was not authored for TUM, the script aborts at start-up with an error such as:

Coalition red has no territory zones and/or controls no airfields. Please add zone with a name starting with REDFOR…

This is not a VEAF bug: it is a TUM design prerequisite. To use it, create in the mission editor a trigger zone whose name starts with BLUFOR and another starting with REDFOR, each containing at least one airbase, plus at least one other mission zone. Only enable TUM for a TUM-style mission.

Opt-in (like MIST, unlike the other community scripts). TUM is off by default: a vanilla mission, a freshly v5-converted mission, or a modules: block that does not mention TUM all leave it disabled. Only an explicit TUM: true turns it on. MIST is opt-in too, for a different reason (see the note above); the remaining community scripts are opt-out (active unless you set them to false). When TUM: true, the build automatically calls TUM.initialize() at start-up — you do not need to add anything to mission-script.lua.

VEAF module IDs:

ID Module Doc page
RADIO veafRadio veafRadio
SHORTCUTS veafShortcuts veafShortcuts
NAMEDPOINTS veafNamedPoints veafNamedPoints
ASSETS veafAssets veafAssets
CARRIER veafCarrierOperations veafCarrierOperations
ASSIST veafAssist veafAssist
SANCTUARY veafSanctuary veafSanctuary
COMBATZONE veafCombatZone veafCombatZone
AIRWAVES veafAirWaves veafAirWaves
QRA veafQraManager veafQraManager
CASMISSION veafCasMission veafCasMission
CAMPAIGN veafCampaign veafCampaign
COMBATMISSION veafCombatMission —
SPAWN veafSpawn veafSpawn
MOVE veafMove veafMove
SECURITY veafSecurity veafSecurity
GRASS veafGrass veafGrass
WEATHER veafWeather veafWeather
INTERPRETER veafInterpreter veafInterpreter
MISSILEGUARDIAN veafMissileGuardian veafMissileGuardian
TRANSPORTMISSION veafTransportMission veafTransportMission
AIRBASES veafAirbases veafAirbases
GROUNDAI veafGroundAI —
REMOTE veafRemote —
SKYNET_MONITOR veafSkynetMonitor —
I18N veafI18n —

modules.RADIO.user_menus — F10 radio menus in YAML

Since ADR 0011, a mission maker can declare a custom F10 radio menu (in particular for the Mission Master) entirely in YAML, with no Lua, under modules.RADIO.user_menus.

modules:
  RADIO:
    user_menus:
      restrict_to_group: "MM Ctrl"   # optional: name of a DCS group; menu restricted to that group. Omitted = global.
      tree:
        - menu: "QRA Control"
          items:
            - { command: "Start QRA North", action: qra.start, qra: "QRA-North" }
            - { command: "Stop QRA North",  action: qra.stop,  qra: "QRA-North" }
        - { command: "Global message", action: message, text: "The mission is starting!" }
        - { command: "Custom function", action: lua, function: "myMission.startEverything", args: ["alpha", 3] }

Each node of tree is either a submenu ({ menu: "...", items: [...] }, recursive) or a command ({ command: "...", action: <verb>, <keys> }). The action vocabulary is closed (qra.start/qra.stop, airwave.start/airwave.stop/airwave.reset, flag.on/flag.off/flag.set/flag.increment/flag.decrement, message, lua). A lua action references a function defined in mission-script.lua: if the function is missing, the build fails.

See the full schema, the action table and a detailed example in veafRadio → Radio menus in YAML.


modules.CAMPAIGN — a campaign mission

Turned on and filled by veaf-tools campaign next, never by hand: it makes the mission one episode of a multi-mission campaign.

modules:
  CAMPAIGN:
    enable: true
    data_file: src/campaign-data.yaml
Key Role
data_file the file, relative to the mission folder, the build turns into the veafCampaign.data table (zones, garrisons, reserves, destroyed scenery); read at build, never handed to the runtime as is

modules.QRA, cap_missions:, combat_missions:

QRA definitions live under modules.QRA (silence_all + definitions:). The cap_missions: / combat_missions: sections remain top-level. All require the corresponding modules enabled under modules:.

See the respective module pages for full schema: - modules.QRA — Quick Reaction Alert definitions - cap_missions: and combat_missions: — CAP and combat mission definitions

Radio menu shortcut (QRA / AirWaves). A QRA definition (modules.QRA.definitions[]) or an AirWave zone (modules.AIRWAVES.airwave_zones[]) accepts radio_menu: true (and the optional radio_menu_restrict_to_group: "<DCS group>") to automatically generate an F10 control submenu (start/stop, plus reset for AirWaves). See veafQraManager and veafAirWaves.


community_scripts: (legacy)

Deprecated. Community scripts are now configured in the unified modules: block using their uppercase IDs (e.g. CTLD: true). The separate community_scripts: section still works but emits a deprecation warning. See modules: for the current syntax and the list of community IDs.


custom_scripts:

Declares custom Lua scripts present in src/scripts/ that are not part of the standard VEAF v6 set.
A declared script is included in the .miz without triggering a warning. By default a DCS load trigger is generated for it automatically; setting generate_load_trigger: false disables that trigger (useful when the script is loaded manually from mission-script.lua).

Field Type Default Description
generate_load_trigger bool true Global default: generate a DCS trigger for all scripts in the list
scripts[].path string (required) Path to the file, relative to the mission folder (e.g. src/scripts/FgMission.lua)
scripts[].generate_load_trigger bool (global default) Per-script override; if absent, the global default applies
scripts[].delay_seconds number (none) Load this script after this delay (seconds) instead of with the rest. See below

Loading behaviour

  • generate_load_trigger: true (default) → the script is loaded at mission start, in both loading modes: in static builds it is embedded in the .miz and loaded by the VEAF mission-loading trigger; in dynamic builds it is loaded from disk by the generated veafDynamicConfig.lua. The same flag governs both modes — there is no per-mode flag.
  • generate_load_trigger: false → the script is still injected into the .miz but no load is generated in either mode; mission-script.lua (or another script) must load it via dofile.

Load order: mission scripts always load as veaf-config.lua → mission-script.lua → your custom_scripts (in declaration order). So a custom script can rely on the VEAF config and on mission-script.lua already being loaded.

custom_scripts:
  generate_load_trigger: true       # global default for all scripts below
  scripts:
    - path: src/scripts/FgMission.lua
    - path: src/scripts/FgTools.lua
      generate_load_trigger: false  # loaded manually from mission-script.lua

Any .lua file present in src/scripts/ but absent from this section (and not one of the standard files) triggers a build warning with a reminder to declare it here.

Loading a script after a delay: delay_seconds

By default every mission script is loaded in one go at start-up. Some scripts need time to pass before they start — typically because they inventory the world once and must let the scripts before them create their units first. AIEN in Foothold is exactly that: loaded 12 seconds after the rest.

custom_scripts:
  scripts:
    - path: src/scripts/Moose.lua
    - path: src/scripts/zoneCommander.lua
    - path: src/scripts/AIEN.lua
      delay_seconds: 12          # its own trigger, 12 s in
  • Absent (the default) → loaded in the shared trigger, exactly as before.
  • Present → the script leaves the shared trigger for a triggerOnce of its own, gated on c_time_after. Scripts sharing the same delay share one trigger, in declaration order.
  • The delay must be greater than zero. A zero, negative or non-numeric value is refused with a build warning and the script loads in the shared trigger instead — it is never lost.

The delay decides the order, not the position in the list. A script at delay_seconds: 12 loads after every undelayed one, wherever it is written. If a delayed script is declared before an undelayed one, the build warns you — the list then reads in a different order from the one it runs in.

Dynamic builds behave the same way: veafDynamicConfig.lua schedules the load instead of doing it inline. Since generate_load_trigger governs both modes, a delay could not exist in only one of them.

convert-other detects these delays in the source mission and writes delay_seconds: for you, so an adopted mission reproduces the upstream staging without you having to notice it.

A script only in one variant (e.g. a dynamic-only debug script)

generate_load_trigger is a single flag — it does not distinguish static from dynamic. To load a script in only one variant (a debug helper you want only in your local dynamic dev build, never in the static distribution), use a build profile instead of a per-script flag:

custom_scripts:
  scripts:
    - path: src/scripts/FgMission.lua   # always loaded (both variants)

profiles:
  DEV:
    custom_scripts:
      scripts:
        - path: src/scripts/FgMission.lua    # ⚠️ must repeat the base scripts
        - path: src/scripts/FgDebug.lua      # extra, dev-only

Build the dev variant with veaf-tools mission build --profile DEV (it loads FgMission.lua + FgDebug.lua); the default build loads only FgMission.lua.

⚠️ Pitfall: profile deep-merge replaces lists, it does not concatenate them (see profiles:). The profile's custom_scripts.scripts must therefore repeat the base scripts and add the variant-specific one — otherwise the base scripts are lost in that profile.


pipeline:

Controls the optional build pipeline steps. See the Pipeline Reference for the full schema of each step's config file.

Each step accepts either a scalar value (true/false to enable or skip the step) or a mapping of detailed options.

pipeline:
  presets: false
  waypoints: true
  spawnable_aircrafts:
    file: src/my-spawnables.yaml
    mode: replace
  dynamic_slot_templates: false
  weather: false

On top of the scalar form, the presets step accepts a mapping that keeps the radio injection while suppressing the PNG kneeboard plates globally:

pipeline:
  presets:
    enabled: true       # default true — inject radio presets; false = disable the whole step
    kneeboards: false   # default true — when false, no kneeboard PNG (KNEEBOARD/<type>/IMAGES/presets[-<coalition>].png) is generated

The dynamic_slot_templates step's shipped catalogue injects only the templates of the sides holding at least one base that offers dynamic slots (a base whose warehouse says dynamicSpawn, or a side warehouses.yaml declares): the others would serve nobody. Your own file is injected as written; when the source warehouses cannot be read, nothing is left out. A side that captures a base in game needs its templates from the start:

pipeline:
  dynamic_slot_templates:
    all_sides: true     # default false — keeps both sides' templates

The waypoints step takes the same kind of mapping, for the automatic injection of the mission's own bullseye (see The bullseye, injected for you):

pipeline:
  waypoints:
    enabled: true       # default true — inject flight plans; false = disable the whole step
    bullseye: false     # default true — when false, no BULLSEYE waypoint is added automatically

build:

Controls how veaf-tools mission build resolves the VEAF scripts bundle. These settings are normally set via the CLI (--dev-mode, --scripts-path) and then persisted here automatically.

Field Type Default Description
dev_mode bool false When true, scripts are loaded from <scripts_path>/build/veaf-scripts.lua instead of the published copy
scripts_path string (user config) Path to a local VEAF-Mission-Creation-Tools clone; required when dev_mode: true
dynamic_loading bool false When true, VEAF and mission scripts are loaded from disk at runtime (dynamic mode, dev/test) instead of being embedded in the .miz. Profile-overridable. CLI: --dynamic-mode / --no-dynamic-mode (takes precedence)

scripts_path resolution order (first match wins): 1. --scripts-path <path> CLI option 2. mission.yaml build.scripts_path 3. ~/veafmct.yaml scripts_path

dynamic_loading resolves as: --dynamic-mode/--no-dynamic-mode (CLI) > build.dynamic_loading (profile-overridable) > false.

Dynamic loading — DEV vs PROD. When dynamic_loading: true, scripts load from disk at runtime (so they are not exposed inside the .miz/.trk):

  • DEV (dev_mode: true) — scripts_path must point at a VEAF-Mission-Creation-Tools checkout; the framework loads the individual veaf/*.lua via VeafDynamicLoader.lua (edit and re-test without rebuilding).
  • PROD (dev_mode: false) — the framework loads the concatenated bundle veaf/veaf-scripts.lua from scripts_path (default <mission>/published, installed by the updater).

In both modes the mission scripts — including your custom_scripts: — are loaded from disk via a generated src/scripts/veafDynamicConfig.lua (do not edit it by hand; declare scripts in custom_scripts:). The build errors out if the framework loader is missing under scripts_path.

build:
  dev_mode: true
  scripts_path: C:/dev/VEAF-Mission-Creation-Tools
  dynamic_loading: false

profiles:
  test:
    build:
      dynamic_loading: true   # dynamic loading for the test profile

See the Developer Mode section of the Developer Guide for the full workflow.


profiles:

Named build profiles. Each profile is a set of config overrides that deep-merge onto the base mission.yaml when you pass --profile <name> to veaf-tools mission build. Keys absent from the profile retain their base values. Lists are replaced, not concatenated. The profiles: key itself is never written to the built mission.

Field Type Description
profiles.<name> dict Any top-level mission.yaml key (e.g. global_log_level, security, pipeline)

Merge rules - Nested dicts are merged recursively (only the keys you specify are overridden). - Scalar values and lists are replaced wholesale; for combat zones, see zone_edits. - profiles: is stripped from the effective config — it is never passed to the Lua generator or the pipeline.

profiles:
  TEST:
    global_log_level: debug
    security:
      disabled: true
    pipeline:
      weather: false
  SERVER:
    global_log_level: info
    pipeline:
      weather: true

Usage:

.\veaf-tools.exe mission build --profile TEST
.\veaf-tools.exe mission build --profile SERVER

If the named profile does not exist in mission.yaml, a warning is emitted and the base config is used unchanged.

Editing a combat zone without copying the list

combat_zones is a list: a profile that gives it replaces it whole. To change two zones, a profile gives modules.COMBATZONE.zone_edits instead, mapping a zone_name (case-insensitive) to the keys to set on that entry. The "*" key sets its keys on every entry; named edits are applied after it. The rest of each entry is kept, and zone_edits never reaches the Lua generator. A name no zone carries is reported by the build and does nothing.

Example: a training version of the mission, with no QRA, two SAM batteries off at start, every zone in training.

profiles:
  TRAINING:
    modules:
      QRA:
        enabled: false
      COMBATZONE:
        zone_edits:
          "*": { training: true }
          SA6Sasa: { active_at_start: false }
          SA11Kiswah: { active_at_start: false }

Removing Mission Editor groups

A profile changes configuration only, except remove_groups:: a list of group names, exact or as patterns ("SA-10 *"), that the build takes out of that profile's .miz — whether placed by hand, carrying a #veafInterpreter, members of a combat zone or statics. A pattern that matches no group is reported by the build.

profiles:
  NO_SA10:
    remove_groups: ["SA-10 *", "EWR Gudauta"]

build_variants:

A list of build profiles to emit together: a single veaf-tools mission build then produces one .miz per variant (the "moulinette" goal — typically Modern and Cold-War from one mission folder, the variant being only a config difference). Each variant builds the full pipeline with its merged profile (see profiles:) and its .miz is suffixed with the variant name (<base>_<VARIANT>.miz).

Field Type Description
build_variants list[str] Profile names (declared under profiles:) to each emit as a distinct .miz.

Rules - Without build_variants: (or an empty list) → a single .miz, behaviour unchanged. - --profile <name> is the escape hatch: it forces building a single variant (that profile), suffixed with its name like a variant — build_variants: is ignored. - Variants build in declaration order; each name must match a profiles: profile (otherwise a warning + base config, like --profile).

profiles:
  MODERN:
    mission:
      era: MODERN
  COLD_WAR:
    mission:
      era: COLD_WAR

build_variants:
  - MODERN
  - COLD_WAR
.\veaf-tools.exe mission build          # produces <base>_MODERN.miz AND <base>_COLD_WAR.miz
.\veaf-tools.exe mission build --profile MODERN   # produces only the MODERN variant (<base>_MODERN.miz)

training:

The mission's training versions, one per role: with this block, the build also produces <base>_TRAINING_A2G.miz, _TRAINING_CAP, _TRAINING_SEAD and _TRAINING_ALL, on the same setting and with the same briefing. elements classifies what the mission holds (enemy_cap, qra, sam_lr, a2g_target, sam_threat); randomize declares the briefing's vague elements, drawn again at every launch. The rules, examples and what is refused: Training versions, one per role.

  • Cannot be combined with build_variants:.
  • --profile TRAINING_SEAD builds that version only.

Keys documented elsewhere

Four top-level keys are read by the build but explained on the page that introduced them. They are listed here so that reading this reference does not miss them.

Key What it does Page that documents it
conversion_profile Names the adoption profile applied to a third-party mission (imposed modules, incompatibilities refused at validation) convert-other
config_override Injects raw configuration values over the ones the profile decided convert-other
strip_native_triggers Lists the original mission's load triggers to remove, so the VEAF scripts are not loaded twice convert-other
dcs_bridge Injects the dcs-bridge.lua bridge into the .miz (enabled, lua_path) — this is what makes data capture from a running DCS possible Mission maker's guide

Index by category

The six domains match the French version, and every top-level section of this page appears in exactly one of them.

Core

Section / Field Description
global_log_level Force a log level on all modules
mission: Mission name, era, export path
settings: Arbitrary veaf.config.KEY = value pairs
veaf_tools: Version compatibility constraint
modules: Enable, disable and configure each Lua module
Keys documented elsewhere conversion_profile, config_override, strip_native_triggers, dcs_bridge

Security

Section / Field Description
security: Enable/disable security, password hashes
modules.SECURITY veafSecurity

Combat

Section / Field Description
modules.QRA QRA definitions
cap_missions: CAP mission definitions
combat_missions: Combat mission definitions
modules.COMBATZONE veafCombatZone
modules.AIRWAVES veafAirWaves
modules.CASMISSION veafCasMission

Air Defense

Section / Field Description
modules.SKYNET Skynet IADS integration
modules.SANCTUARY veafSanctuary
modules.MISSILEGUARDIAN veafMissileGuardian

Assets & Support

Section / Field Description
modules.ASSETS veafAssets
modules.CARRIER veafCarrierOperations
modules.NAMEDPOINTS veafNamedPoints
modules.RADIO veafRadio
modules.RADIO.user_menus F10 radio menus declared in YAML
modules.SHORTCUTS veafShortcuts
modules.ASSIST veafAssist
modules.CTLD / modules.CSAR Cargo transport and pilot recovery (third-party sidecars)

Build Pipeline

Section / Field Description
pipeline: Pipeline step control
pipeline.presets presets.yaml schema
pipeline.waypoints waypoints.yaml schema
pipeline.spawnable_aircrafts / pipeline.dynamic_slot_templates aircraft groups schema
pipeline.weather versions.yaml schema
custom_scripts: Custom Lua scripts to include in the mission
community_scripts: Bundled community scripts (legacy form)
build: Developer mode and scripts path override
build.dev_mode Use local Lua bundle instead of published scripts
build.scripts_path Path to local VEAF-Mission-Creation-Tools clone
profiles: Named build profiles (deep-merge overrides for --profile)
build_variants: Produce one .miz variant per named profile
training: Training versions, one per role, built with the mission

Index by module

Module mission.yaml key Doc page
veafRadio modules.RADIO veafRadio
veafShortcuts modules.SHORTCUTS veafShortcuts
veafNamedPoints modules.NAMEDPOINTS veafNamedPoints
veafCarrierOperations modules.CARRIER veafCarrierOperations
veafAssets modules.ASSETS veafAssets
veafAssist modules.ASSIST veafAssist
veafSanctuary modules.SANCTUARY veafSanctuary
veafCombatZone modules.COMBATZONE veafCombatZone
veafAirWaves modules.AIRWAVES veafAirWaves
veafQraManager modules.QRA veafQraManager
veafCasMission cap_missions: + combat_missions: veafCasMission

See Also