Skip to main content
Glama
ALasek
by ALasek
README.md
# DCS Mission Authoring Gateway

`dcs-me-gateway` is a local MCP server between an AI mission designer and the
[DCS-SMS](https://github.com/nielsvaes/dcs-sms) Mission Editor bridge. It turns
many fragile editor commands into a small, versioned authoring protocol.

The gateway does not click the UI and never focuses DCS. Requests are written
to the existing DCS-SMS mailbox. If DCS is unfocused and its Mission Editor
update loop is throttled, a request remains queued until ticks resume or the
caller-selected deadline expires.

## Implemented in 0.9

- Thirty-six MCP tools: update discovery, mission authoring/validation, persistent semantic map and hydrology preprocessing, offline queries, live map inspection, live airbase and carrier-package allocation, native transport routing, DCS-authored loadout discovery, live AI-task discovery, flight-profile/timing and fuel-policy planning, package templates, drawings, deterministic mission documents, optional Skynet-IADS setup, and bounded offline external-content management.
- Forty-one declarative patch operations for the mission environment, groups, units, payloads, routes, typed waypoint options/behavior, named group Triggered Actions, triggers, circular zones, group frequencies, and unit callsigns.
- Stable logical references, including adoption of existing named mission entities and DCS-allocated unit names.
- The mission-design guidance is advertised as MCP server initialization
  instructions, not only as an optional resource/prompt, so compatible clients
  receive the planning, timing, loadout, and AI-policy defaults automatically.
- Compact semantic snapshots, deterministic revisions, optimistic concurrency, and per-operation postcondition checks.
- Per-mission bindings and idempotency records protected by atomic writes and inter-process locking.
- Absolute request expiry and explicit `OUTCOME_UNKNOWN` handling prevent late execution and unsafe replay after a lost response.
- A validated patch is dispatched in one Mission Editor callback; partial committed prefixes are recorded if a later verb fails.
- Prefab-shaped mission fragments use a dedicated DCS-SMS transaction endpoint
  with native ID/name allocation, cross-reference remapping, rollback, and
  editor-side replay protection.
- Save/reopen verification waits for a stable semantic revision and migrates bindings across Save As identity changes.
- Fixed compatibility guards for DCS-SMS-created vehicle routes and stale Mission Editor route-panel ownership.
- No arbitrary-Lua MCP tool.
- Revision-tracked F10 map annotations support verified batches of circles,
  lines, polygons, text labels, and removals without exposing a tool per shape.
- A data-driven mission-template catalog returns tunable S-300PS/Buk site
  layouts and staged AWACS/tanker racetracks. The two SAM packages are live
  checked against the installed DCS unit database; they are functional,
  representative starting points rather than exact real-world orders of battle.
- Carrier-package authoring discovers live Supercarrier entitlement, available
  free/paid hulls, and carrier-capable aircraft from DCS's own runway-category
  metadata. One transaction can place a carrier alone or in a representative
  blue/red escort formation, calculate wind over deck, author supported
  TACAN/ICLS/Link 4/ACLS actions, bind aircraft starts and optional recovery
  waypoints to the deck, and apply named loadouts plus combat consumables.
- Basic communications authoring writes group frequencies and normalized unit
  callsigns. Offline validation warns when AWACS/tanker groups share a frequency.
- Cached terrain can answer coordinate/elevation profiles and straight-line
  terrain LOS with explicit clearance and obstruction points. Buildings and
  vegetation remain outside that model.
- Mixed-mode authoring includes a deterministic 1024×1440 military-light player
  dossier and a copy-on-write `.miz` external-content overlay. The dossier reads
  callsign, aircraft, route, timing, radios, carrier aids, stores, fuel, gun, and
  countermeasures from a pinned saved mission; optional tactical prose remains
  explicit agent input. Route pages use clean cached DCS terrain with semantic
  and F10 overlays disabled. The overlay manages aircraft/general kneeboards,
  briefing text and pictures, and bounded image/sound/Lua resources without
  editing the source mission in place.
- A resumable tile cache extracts authoritative DCS elevation and surface type,
  derives slope, aspect, roughness, and candidate ridge/valley/pass features,
  and renders both a clean terrain PNG and an annotated context PNG without
  requiring DCS for later queries.
- An independently resumable hydrology layer samples DCS surface type at 50 m
  by default without rebuilding elevation, slope, or semantic regions. It packs
  each sample into two bits, skips high-confidence uniform land and open-sea
  base tiles, derives bounded eight-connected river/lake components with
  separately reported conservative short-gap stitching, and can
  expand from a validated slice to full-dataset coverage with stable tile IDs.
- Each cache stores DCS-authored named places and fixed-airbase metadata,
  including runway thresholds/lengths, parking envelopes, shelter flags, and
  derived apron clusters. Place labels remain explicitly unranked
  because DCS's towns source provides names and coordinates, not importance.
- A bounded anchor graph caches DCS-native road and railroad corridors between
  nearby airbases and named places, including detour, elevation/grade, endpoint
  snap, and water-crossing evidence. Live routing remains available for precise
  endpoints because the cache is not a complete road-network export.
- Rough settlement regions are buffered named-place clusters with explicit
  provenance and recommended image-view bounds. They are not building
  footprints; individual structures remain a visual map-inspection task.
- The semantic cache also stores queryable airbase, settlement, and named-place
  entities plus contiguous terrain regions classified from surface, elevation,
  slope, roughness, and low-detail evidence. Regions use compact row runs and
  record adjacency rather than pretending that a raster cell is a real-world
  administrative boundary.
- A resumable macro transport graph uses airbase and settlement hubs. Its
  edges are DCS-native road/rail paths with snapped endpoint gateways, so an
  offline query can select a multi-edge regional route while live DCS routing
  remains the authority for the exact first/last-mile convoy geometry.
- On-demand map inspection fits the live ME map to requested bounds, captures
  only its map widget, writes a georeferenced PNG, and restores the previous
  camera. A camera-motion frame check rejects hidden-editor captures (for
  example, when a running mission's F10 view is actually on screen).
- Preprocessing automatically checks cache shape, exact shared tile edges, and
  16 deterministic points against live DCS. The same report can be rerun with
  `validate_map_dataset`, or with live parity disabled for offline-only QA.
- Conservative low-detail detection records its evidence and only stores a
  coarse tile when near-uniform land passes every threshold; it does not
  silently equate ordinary flat terrain with Caucasus edge placeholders.
- DCS-saved golden fixtures, offline archive/Lua/reference tests, and an isolated live authoring harness that edits a clone, restores the original, and verifies its file hash.
- Live airbase planning uses DCS's aircraft-envelope/category filter and the
  open mission's reserved stands, mission surface wind, runway labels, and a
  stand-to-likely-threshold taxi proxy. Verified creation supports cold, hot, and
  runway starts for one-to-four aircraft and removes a partially built group
  if a construction step fails. Active-runway strategy can reassign the native
  compact parking choice to the verified preferred stand set.
- Preset-first loadout authoring queries the installed DCS build for named
  aircraft presets, their declared task compatibility, stores, and weight.
  Patches can apply one preset or make a bounded per-pylon adjustment; the
  gateway never asks the model to invent CLSIDs or an entire payload.
  Combat authoring also explicitly checks gun ammunition; a zero-gun combat
  aircraft is warned unless that omission is intentional.
- Live task discovery lists only the waypoint and enroute actions DCS exposes
  for an existing group, and task descriptions provide defaults, allowed
  values, and missing target-selector guidance before a patch is authored.
- Mission date/time and useful weather fields are readable in every semantic
  snapshot and writable as one revision-checked environment operation. Typed
  waypoint options include Reaction on Threat, ROE, radar/ECM use, bingo RTB,
  silence, and jettison/afterburner prohibitions.
- Role-based fixed-wing profiles and transparent startup/taxi/climb/transit
  estimates help synchronize package arrival. Offline lint warns when an AI
  scramble/intercept begins its transit low or slow.
- Offline behavior lint warns about unarmed AI combat aircraft, combat groups
  without engagement tasking, target tasks without target IDs, semantic
  waypoint names without corresponding behavior, and air routes without a
  terminal landing/orbit/loop. Objective lint also warns about opposing outcomes
  that can both fire, capture conditions satisfied by one surviving group member,
  briefing-named groups omitted from every recognized outcome condition, and
  intercept waypoints assigned to non-air groups. These are heuristic authoring
  warnings, not claims about DCS runtime AI correctness.

The gateway does **not** claim rollback for declarative patches. DCS-SMS patch
verbs are run sequentially in one ME callback; if verb N fails, verbs 1 through
N-1 may already be committed. The result records the committed prefix and the
idempotency key, preventing a blind duplicate retry. The separate
`apply_mission_fragment` endpoint has native DCS-SMS rollback and reports an
incomplete rollback explicitly. If its acknowledged result or post-apply
snapshot cannot prove the requested outcome, the gateway returns
`OUTCOME_UNKNOWN` and requires mission inspection before recovery; it never
pretends that a failed verification undid the mutation. A future transactional
patch verb is described in [`docs/dcs-sms-extension.md`](docs/dcs-sms-extension.md).
Because the live semantic revision does not include warehouse contents, the
gateway conservatively blocks cached replay of fragments that contain warehouse
effects; inspect the mission before choosing a new key. Drawings are included
in semantic revisions and save/reopen verification.

Revisions and save/reopen verification cover the protocol 0.1 authoring
surface: semantic environment, group, unit, route, trigger, zone, and drawing
records, plus mission path and theatre. Briefing, warehouses, and global
mission options are outside the current revision boundary.

DCS persists vehicle headings at group level. The semantic snapshot therefore
uses the lead vehicle's heading for every unit in that vehicle group. An
attempt to give only a non-lead vehicle a different persistent heading fails
postcondition verification instead of silently drifting after reopen.

## Requirements

- The Windows release includes its own Node.js runtime. Source development
  requires Node.js 20.17 or newer.
- Windows, DCS World, and the DCS-SMS ME-mod for live-editor tools only.
- Mission Editor open with `DCS-SMS -> External execution` enabled for mutation.
- `inspect_map_view` additionally requires the Mission Editor map to be open in
  a non-minimized window. DCS may remain unfocused or covered; the gateway
  captures its application window directly, never brings it to the foreground,
  and does not silently fall back to an occlusion-sensitive monitor capture.

Map extraction requires the typed `terrain info`, `terrain sample-grid`, and
`terrain list-places`, `terrain nearest-transport`, `terrain transport-path`,
`airbase plan-start`, and `airbase create-start` verbs in the accompanying
DCS-SMS ME-mod `0.32.4` worktree.

## Install the Windows release

The release is one ZIP. It includes the gateway, locked production dependencies,
a portable Node runtime, and the compatible DCS-SMS Mission Editor bridge source.
It does not require a separate DCS-SMS installation and does not install the
DCS-SMS mission hook or optional LuaSec/OpenSSL files.

Extract the ZIP, close DCS, and run:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -Clients codex
```

Supported `-Clients` values are `codex`, `claude-code`, `claude-desktop`, and
`generic`; more than one may be passed. `generic` writes a portable
`mcp-config.json`. Codex and ChatGPT desktop share Codex's local MCP
configuration. Claude Code and Claude Desktop are registered independently.
Other clients can use the same stdio command and environment from the generic
file. ChatGPT on the web cannot directly launch a local stdio server; this
package targets ChatGPT desktop/Codex on the machine running DCS.
See the current [OpenAI MCP documentation](https://developers.openai.com/codex/mcp/)
and [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp)
for their native configuration surfaces.

The installer:

- discovers DCS and Saved Games or accepts `-DcsPath` and `-SavedGames`;
- verifies DCS has `Full Screen` disabled, but never edits that preference;
- refuses to patch the Mission Editor while DCS is running;
- backs up the user's `MissionEditor.lua` and any previous bridge;
- installs the bundled GPLv3 bridge source and adds only its delimited require
  block to the user's own editor file; and
- registers only the `dcs-me` MCP entry requested by the user and pins that
  entry to the selected Saved Games instance.

At MCP startup the gateway checks GitHub's latest stable-release endpoint, with a
2.5-second timeout and a six-hour local cache. When a newer semantic version is
available, its MCP instructions tell the agent to ask the user whether to
update. The check never installs anything. After explicit approval, close DCS
and every MCP client using the gateway, then run:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File `
  "$env:LOCALAPPDATA\DcsMeGateway\update.ps1"
```

The updater asks again before mutation, downloads only the versioned Windows
release, verifies its separately published SHA-256, and invokes the normal
backup-preserving installer. Set `DCS_ME_GATEWAY_UPDATE_CHECK=0` to disable the
automatic check. `check_gateway_update` performs an on-demand or forced check.
Prerelease tags are intentionally excluded from this update channel.

For an agent-guided install, hand the extracted folder to an agent and ask it to
follow [`VIBEINSTALL.md`](VIBEINSTALL.md). After installation, restart the MCP
client, open a mission in DCS Mission Editor, and enable
`DCS-SMS -> External execution`. That toggle is session-only.

The gateway is MIT. The bundled DCS-SMS bridge remains GPLv3 and ships with its
complete source and license under `payload/bridge`; see
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).

## Install from source and verify

```powershell
cd C:\path\to\dcs-me-gateway
npm install
npm test
npm run build
npm run test:golden
npm run test:live
```

Everything through `test:golden` is fully offline; DCS can remain closed. Validate any
saved mission independently with:

```powershell
npm run validate:miz -- D:\Missions\mission.miz
```

The validator checks ZIP integrity and expansion limits, required entries,
literal Lua syntax, mission structure, IDs/names, routes, coordinates, entity
references, dictionary keys, and packaged resources. It parses Lua into data
without loading or executing mission code.

Given the declarative patch and saved files from immediately before and after
it, produce a focused acceptance report with:

```powershell
npm run report:patch -- patch.json before.miz after.miz [bindings-before.json] [bindings-after.json]
npm run docs:render -- --mission <saved.miz> --dataset <dataset-id> --output <new-directory>
```

The report contains direct patch targets, one-hop dependency context,
operation postconditions, introduced/resolved validation findings, and any
changed entities outside that scope. It exits nonzero if the after-file is
invalid, a postcondition fails, a dependency changed unexpectedly, unrelated
mission metadata/options/warehouses changed, or an out-of-scope entity changed.
Patches that refer to logical IDs created earlier must provide the relevant
`{"entities": ...}` binding object (before and, if allocations changed, after);
unresolved IDs are reported explicitly and fail acceptance.

`test:live` reads only the editor session. Add `-- --snapshot` for a live,
read-only mission snapshot.

`npm run test:live-authoring` is intentionally mutating. It requires a saved
mission already open in the Mission Editor. The harness first Save-As clones
that mission under `.artifacts/live`, edits only the clone, checks idempotency,
revision conflicts, route editing, zones, and save/reopen stability, then
reopens the original and verifies that its SHA-256 did not change. It does not
launch or fly the mission.

Start the MCP server over stdio:

```powershell
node C:\path\to\dcs-me-gateway\build\index.js
```

An MCP host should configure that command and path as a stdio server named
`dcs-me`. Diagnostics go to stderr; stdout is reserved for MCP frames.

## Configuration

| Environment variable | Default | Meaning |
|---|---|---|
| `DCS_ME_GATEWAY_SAVED_GAMES` | auto-detected | Explicit DCS Saved Games instance root |
| `DCS_SMS_SAVED_GAMES` | auto-detected | Compatible DCS-SMS override; also accepted |
| `DCS_ME_GATEWAY_STATE` | `<DCS_SAVED_GAMES>\dcs-me-gateway` | bindings and idempotency state |
| `DCS_ME_GATEWAY_MAP_CACHE` | `<DCS_ME_GATEWAY_STATE>\maps` | persistent terrain tile datasets and previews |
| `DCS_ME_GATEWAY_MAP_REGISTRY_FILE` | packaged `maps\registry.json` | bundled map-pack registry and remote-registry pointer |
| `DCS_ME_GATEWAY_MAP_REGISTRY_URL` | registry pointer or none | explicit HTTPS map-pack registry override |
| `DCS_ME_GATEWAY_TIMEOUT_MS` | `60000` | live-smoke default deadline |
| `DCS_ME_GATEWAY_POLL_MS` | `100` | mailbox polling interval |
| `DCS_ME_VIEWER_PORT` | `4317` | localhost-only semantic map viewer port |

Without an override, the gateway reads DCS-SMS's `saved_games` config value,
then chooses the `DCS`, `DCS.openbeta`, or `DCS.server` instance with the
newest ME heartbeat. Set `DCS_SMS_SAVED_GAMES` when Windows Saved Games has
been relocated and DCS-SMS has not recorded that path in its config.

## MCP tools

| Tool | Mutation | Purpose |
|---|---:|---|
| `create_or_open_mission` | Yes | New/open lifecycle with a semantic stability barrier |
| `get_editor_session` | No | Permission, lifecycle, heartbeat, queue status |
| `get_catalog` | No | Capabilities, typed AI options, flight and fuel policies, live airbases, carrier entitlement/hulls/aircraft, trigger predicates |
| `plan_flight_profile` | No | Role-based altitude/speed/option defaults and transparent arrival estimate |
| `plan_fuel_policy` | No | Finite, near-zero exhaustion guard, relief rotation, or deliberate unlimited-fuel plan; can emit patch operations |
| `get_mission_template` | No | List/resolve tunable SAM, prepared-site SAM/AAA, AWACS, and tanker authoring plans |
| `get_aircraft_loadouts` | No | Live DCS-authored presets, task compatibility, stores, weight, and optional station detail |
| `get_ai_task_catalog` | No | Live waypoint/enroute tasks allowed for an existing group |
| `describe_ai_task` | No | Live fields, defaults, allowed values, and target-selector guidance for one task |
| `get_mission_snapshot` | No | Normalized mission state plus revision |
| `validate_miz_file` | No | Offline structure, reference, AI-behavior, and objective-semantic validation |
| `create_patch_report` | No | Focused before/after `.miz` acceptance report for a declared patch |
| `validate_mission_patch` | No | Compile and preflight a patch |
| `apply_mission_patch` | Yes | One-tick, revision-checked, idempotent batch |
| `apply_mission_fragment` | Yes | Native DCS-SMS transaction with rollback; uncertain postconditions require inspection |
| `setup_carrier_package` | Yes | Entitlement-aware blue/red carrier/escort template, WOD, radios/beacons, deck-linked aircraft, and preset loadouts |
| `apply_map_annotations` | Yes | Revision-tracked bulk F10 circles, lines, polygons, labels, and removals |
| `apply_miz_overlay` | New file | SHA-pinned copy-on-write kneeboards, briefing text/pictures, and typed external resources; DCS may be closed |
| `setup_iads` | New file | With explicit user acceptance, embed pinned MIST/Skynet runtimes and a validated prefixed IADS configuration; DCS may be closed |
| `inspect_miz_external_content` | No | Resolve briefing text/pictures, kneeboards, map resources, archive presence, and mission reference counts offline |
| `render_mission_documents` | New previews | Deterministic per-aircraft mission-card, comms/loadout, waypoint, and clean route-map PNG pages |
| `save_and_verify` | Yes | Save-as, reopen, and semantic diff |
| `list_map_datasets` | No | List persistent terrain caches; fully offline |
| `ensure_map_dataset` | Download/cache write | Required theatre check: reuse local, fetch and verify hosted pack, or return an explicit permission gate before a local build |
| `preprocess_map_region` | Cache write | Bounded/resumable live DCS terrain extraction and derivative generation |
| `preprocess_hydrology_layer` | Cache write | Separate resumable high-resolution river/lake surface mask; uniform low-detail land and open sea are skipped |
| `query_map_context` | Preview only | Offline terrain, airbases, named places, feature candidates, and annotated PNG context |
| `inspect_map_view` | Temporary camera | Georeferenced on-demand PNG of the visible live ME map with camera restoration and visibility proof |
| `analyze_terrain_line` | Usually no | Tri-state cached terrain LOS; only `refinement_mode=auto-live` needs matching DCS ME state when finer data is required |
| `validate_map_dataset` | Report only | Tile/seam validation plus optional deterministic live DCS point parity |
| `query_airbase_context` | No | Offline runway, stand, shelter, size-band, and apron context |
| `query_transport_context` | Preview only | Offline sampled road/rail corridors, crossing candidates, settlement regions, and image-view guidance |
| `route_ground_transport` | No | Precise live road path or conservatively validated DCS rail candidate |
| `plan_airbase_start` | No | DCS-native compatibility/occupancy plus wind/runway/taxi/takeoff planning |
| `create_airbase_start` | Yes | Revision-checked, idempotent, verified compact or active-runway parking/runway group creation |

### Map-pack lifecycle

Before map-specific mission planning, the mission agent is instructed to call
`ensure_map_dataset` for the theatre. The tool first reuses a complete local
dataset, then checks the packaged registry and its optional GitHub-hosted remote
registry. A download is streamed, bounded by its registered byte count, checked
against its registered SHA-256, safely extracted, validated for dataset/theatre
identity and completeness, and published atomically. The packaged registry at
[`maps/registry.json`](maps/registry.json) is also the canonical remote registry.
Large datasets are immutable, SHA-256-pinned GitHub release assets; raw caches
and generated archives are never committed to Git.

If no pack is registered, the tool returns `permission_required` rather than
opening DCS or starting hours of extraction by itself. Only after the user
approves may the agent repeat the call with `allow_local_build=true`. The
requested theatre must be installed and open in Mission Editor with external
execution enabled. The build is resumable and may require repeated calls.

Developers can explicitly build and package an unhosted theatre:

```powershell
npm run maps:settlements
npm run maps:build -- --theatre Caucasus
npm run maps:package -- --dataset caucasus-full-v01-250m `
  --base-url https://github.com/ALasek/DCS-ME-gateway/releases/download/maps-v0.9.0
```

`maps:settlements` is a fast, independent review pass for the theatre currently
open in Mission Editor. It resolves the matching curated catalog through DCS's
authoritative coordinate conversion, merges DCS-native town labels, and writes
JSON plus a labelled PNG under `.artifacts/settlements`. It does not sample or
rebuild terrain, elevation, land cover, hydrology, transport, or airbase data.
Use `--output <directory>` to choose a stable output location.

The packaging command prints the exact registry entry, including archive size
and SHA-256. Add that entry to `maps/registry.json` and upload the `.tgz` to the
matching release. Local building is the fallback for maps the project author
does not own; it is never implicit consent.

`preprocess_map_region` validates the requested rectangle against DCS's
authoritative theatre bounds. DCS's 4,096 limit applies to samples in each
terrain-grid request, not to dataset tiles. `max_tiles` is only a deliberate
dataset-size guard (50,000 by default); `tile_batch_limit` bounds each MCP
invocation to 200 missing tiles by default. If the result has `complete=false`,
repeat the call with identical dataset geometry and sampling settings. Each
tile and manifest update is already durable, incomplete datasets reject normal
queries, and final validation can evict unreadable, malformed, or seam-invalid
tiles for automatic repair on the next batch.
Batch size and safety-guard changes do not change an automatically generated
dataset ID.
Its `north/east` coordinates are the same theatre-local metres used by mission
objects. The default conservative prepass may retain high-confidence uniform
sea or placeholder-like uniform land at coarse resolution; every such decision
is visible in the manifest and query result. Coastlines, mixed surfaces, and
flat terrain that do not satisfy all thresholds remain full resolution.
Airbases and DCS-authored place labels are cached beside the
terrain tiles and annotated on previews. Place records are not classified as
city/town/village because the DCS source does not expose that distinction.
Semantic provenance records the DCS build, locale, and theatre towns source;
place IDs are coordinate-derived because duplicate Lua keys are overwritten
when DCS loads some theatre datasets. A changed DCS terrain build requires an
explicit `force=true` rebuild instead of silently reusing stale tiles.
`query_map_context`, `query_airbase_context`, and `query_transport_context` read only the cache, so DCS may
be closed. The 200 m apron clustering and compact/standard/large envelope bands
are navigation heuristics, not DCS compatibility claims. `plan_airbase_start`
always rechecks the requested aircraft and current occupancy through DCS before
mutation.

`render_mission_documents` is deterministic for the same saved mission, terrain
cache, style version, selected groups, and supplied prose. By default it selects
Player/Client flights. It produces a manifest and ready-to-pass
`kneeboard_images` entries but does not modify the mission. DCS kneeboard folders
can target all aircraft or an aircraft type; they cannot distinguish two flights
using the same type, so combine those instructions or use briefing pages when
flight-specific separation is required. The mission card identifies the aircraft
type without implying a per-flight quantity. The comms/loadout page uses canonical
human-readable store names, fuel in pounds, and gun ammunition as a percentage;
it never displays internal CLSIDs. If the saved mission has stores but no readable
preset name, call `get_aircraft_loadouts` with `include_stations=true` and pass its
DCS-authored display names in `flight_notes.loadout_summary`. Rendering then emits
an explicit warning until those names are supplied. Waypoint tables use verified
saved-route ETA values where the route is genuinely scheduled; state-gated or
player-paced events can instead use `flight_notes.waypoint_notes[].time_label`
for cues and timing windows without inventing a fixed clock time. Rendering warns
when a multi-waypoint player route has neither useful ETA values nor time labels.
Route maps add only sparse
airbase and major-settlement labels, with route and waypoint labels retaining
collision and visual priority. Preview every page before embedding it.

`apply_miz_overlay` is deliberately not a general mission-table editor. Save
the live ME mission, close or open a different file, inspect existing external
content, pin that source file's reported SHA-256, and write to a new output path.
The tool validates real image/sound/script signatures, refuses referenced
resource deletion, and garbage-collects replaced briefing pictures only when no
mission reference remains. After the overlay succeeds, open the output in ME
and run `save_and_verify`. Core groups, routes, triggers, and objectives remain
ME-authored; this prevents two writers from silently diverging on the same
`.miz`.

### Optional Skynet-IADS

For a meaningful multi-site/EWR network, SEAD/DEAD scenario, or mission whose
balance depends on radar-emission tactics, the agent may recommend Skynet once
and ask whether the user accepts runtime-script-dependent behavior. It should
not interrupt every isolated-SAM design with that question. Native DCS alarm
states and deterministic emission triggers remain the default for isolated,
predictable, or training threats.

After acceptance, give managed SAM group names one common prefix and managed
EWR unit names a distinct prefix, save the mission, and call `setup_iads` with
the saved file's SHA-256 and a new output path. The tool verifies and embeds
Skynet-IADS 3.3.0 with its upstream-tested MIST 4.5.107, then adds one Mission
Start trigger loading MIST, Skynet, and the generated configuration in that
order. The resulting `.miz` is self-contained; players install nothing. Open
the output in Mission Editor and run `save_and_verify` before continuing.

The setup validates script hashes, load order, coalition membership, prefixes,
and named command centers. It cannot prove runtime IADS behavior without
running the mission. Keep one SAM system type per managed SAM group, one EWR
radar per managed EWR group, and do not add native emission actions that fight
Skynet for the same radars. Provenance, exact hashes, and licenses are recorded
under [`vendor/skynet-iads/3.3.0`](vendor/skynet-iads/3.3.0) and
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).

`analyze_terrain_line` bilinearly interpolates the finest cached grid covering
each sample and reports `clear`, `blocked`, or `indeterminate`. The classifier
uses an explicit elevation-resolution envelope: clear and blocked require the
entire envelope to stay on one side of the sightline. Cached-only mode reports
the uncertain corridor and recommended spacing without contacting DCS.
`refinement_mode=auto-live` samples that bounded corridor from a matching open
Mission Editor theatre, splits it to the extension's per-call sample limit,
persists the finer tiles under the dataset, and immediately reruns the LOS.
Later queries reuse those tiles with DCS closed. A fine rerun may legitimately
remain indeterminate; the response reports a resolution limit and, where
possible, a smaller next spacing rather than inventing a clear/blocked answer.
This is suitable for radar/SAM
placement and route screening, but not final visibility claims where buildings
or trees matter.

After the terrain tiles finish, semantic preprocessing derives a 2 km
classification raster, smooths isolated non-water cells, and converts each
four-connected component into a compact terrain region with statistics,
adjacency, and row-run geometry. Airbases and settlement clusters become graph
hubs. Road candidates include up to four neighbors within 80 km plus bounded
connectivity repair; railroad candidates include up to two neighbors within
120 km. DCS railroad routing is treated as candidate evidence because its
terrain-network selector can return unrelated linear features. A cached rail
corridor must snap within 1.5 km of both anchors and its routed length must cover
at least half their direct separation; weaker results remain failed candidates
for inspection instead of being exposed to the agent as rail.
`transport_batch_limit` bounds live DCS path extraction per invocation,
and pending/complete/failed edge state is durable and resumable. Completed
polylines retain hub IDs and snapped gateway points. `query_transport_context`
uses Dijkstra over completed cached edges for an offline named-hub route;
`route_ground_transport` applies the same conservative rail checks to exact
current DCS paths used for mission authoring. Water-surface runs are labeled
`water-crossing-candidate`, not
asserted to be destructible bridge objects. Settlement clustering deliberately
avoids a per-building catalogue, which would be terrain-version-sensitive and
unnecessarily large.

The Persian Gulf processor supplements DCS's own town labels with the sparse,
editable catalog at [`maps/settlements/persian-gulf.json`](maps/settlements/persian-gulf.json).
It contains major cities, ports, and island settlements selected for an
approximately 1980-1990 scenario vocabulary; it is not a historical urban
footprint. During preprocessing, DCS converts each WGS84 point into the loaded
theatre's local coordinates. Matching nearby DCS labels retain their DCS
position while gaining the curated name, aliases, type, and importance. A
standalone curated point remains regional semantic evidence only. Catalog
SHA-256 and source/era provenance are stored in `semantics.json`, so editing
the catalog invalidates and rebuilds cached settlement and transport semantics
without rebuilding elevation tiles. See [`maps/settlements/README.md`](maps/settlements/README.md)
for the intentionally simple cleanup format.

`query_map_context` may be called with bounds, a `semantic_search`, or both.
Semantic search can filter terrain classes, minimum region area, proximity to
an entity, entity kind/name, and approximate road access without loading terrain tiles or
requiring DCS. `inspect_map_view` is the complementary detail path: it does not
cache every building or road marking, but returns the current ME rendering at
the requested scale with an affine pixel-to-theatre transform. It temporarily
writes the ME map camera, serializes against other gateway mutations, and
restores the prior camera even when capture or visibility validation fails.
It resolves the exact DCS window handle and fails with window geometry and
recovery details when DCS is minimized or an exclusive-fullscreen Alt-Tab has
removed its capturable surface. Borderless/windowed DCS may stay behind the MCP
client during inspection.
If an airbase, settlement cluster, and raw DCS place share a name, use the
returned entity ID or a `kind:name` selector such as `settlement:Batumi` or
`airbase:Batumi`; ambiguous bare names fail instead of silently selecting one.

Run the local layered cache viewer with `npm run viewer` during development or
`npm run viewer:start` after `npm run build`, then open
`http://127.0.0.1:4317`. It visualizes the clean cached topography, contiguous
terrain regions, usable and failed transport edges, settlement hulls, semantic
entities, and labels without contacting DCS.

`validate_map_dataset` streams tile readability and array checks, compares only
adjacent shared edges, and does not materialize the full map in memory. Its
default `direct_samples=16`
compares evenly distributed cached points with the currently loaded DCS
theatre; use `direct_samples=0` for the offline-only checks. The report is also
saved as `validation.json` under the dataset directory.
The completed dataset overview is likewise painted from one tile at a time
into its bounded output raster.

The bounded Caucasus development harness deliberately samples the detailed
Batumi-Kobuleti coast-to-mountain strip rather than the map's flat outer area:

```powershell
npm run test:live-map
```

It samples `north=-370000..-330000`, `east=610000..670000` at 250 m,
persists progress after every tile, and is safe to rerun. Add `-- --force` only
to deliberately refresh already cached tiles. It does not process the whole map.

### Hydrology refinement

The 250 m terrain cache is intentionally not treated as authoritative river
geometry: a narrow river can fall between grid points and appear as sparse blue
dots. `preprocess_hydrology_layer` attaches a separate `hydrology/` cache to an
existing completed dataset. Its default 50 m grid stores only a compact two-bit
surface code per sample; elevation and terrain derivatives remain at their base
resolution. Base tiles already proven to be uniform placeholder land or open sea
are omitted from planning, while detailed and mixed coastal tiles are sampled.

Start with representative bounds and repeat identical calls until
`completeForBounds=true`. Later omit `bounds` to expand the same layer over all
detailed base tiles. `tile_batch_limit` bounds each invocation and `max_tiles`
is only a deliberate safety ceiling. A changed spacing, tile size, theatre, or
DCS terrain signature fails closed; `force=true` deliberately begins a new
hydrology refresh and subsequent resumable calls use `force=false`.
`query_map_context` returns coverage, exact bounded surface counts, deterministic
stitch metrics, and sampled eight-connected river/lake components when the layer exists.
Its preview and clean documentation maps incorporate the finer water mask.

The original DCS codes and surface counts remain immutable evidence. An offline
directional stitcher may fill at most two missing cells when source samples on
both ends continue along the same line; inferred cells are reported separately
and are used only for component continuity and rendering. This is deliberately
more conservative than unrestricted morphological closing, which can merge
nearby unrelated waterways. Component geometry is neither a bank polygon nor a
navigation guarantee. Rivers narrower than the chosen spacing can still be
fragmented, so use a tighter bounded pass or live map inspection when exact bank
placement matters. High-confidence coarse placeholder-land and uniform-sea base
tiles are outside hydrology coverage by design; this optimization assumes the
base low-detail calibration has already separated the map's non-mission terrain
from real detailed plains. The viewer exposes the generated layer as a
separately toggleable `Rivers / lakes` overlay. Presentation is derived rather
than evidentiary: lakes retain a smoothed sampled-area raster, while river bands
are deterministically thinned, conservatively gap-joined, simplified, and drawn
as anti-aliased SVG centerlines with a fixed screen-space width. Queries and
validation continue to use the original packed cells and separately identified
stitch points.

The complete patch schema is also exposed as the MCP resource
`dcs-me://schema/mission-patch/0.1`. See
[`examples/f16-rwr-patch.json`](examples/f16-rwr-patch.json) and its subsequent
[`examples/f16-rwr-route-patch.json`](examples/f16-rwr-route-patch.json).

Whole-mission authoring should begin with the MCP prompt
`design_dcs_mission`. It guides a conversation rather than demanding a
one-shot specification: the agent may ask high-impact questions, recommend
defaults, suggest alternatives, and maintain a living player role, outcome,
phase, per-group purpose, causal interaction, capability/loadout, and balance
record. The canonical human-readable instruction is
[`docs/design-dcs-mission.md`](docs/design-dcs-mission.md); the server loads that
same file for initialization instructions, the MCP prompt, and the guidance
resource. A coherent backbone and first slice precede mutation, but later details
may remain open while approved slices are authored and tested. The same
instruction is available as the read-only resource
`dcs-me://guidance/mission-design/0.1`. Mission concepts are developed with the
user and implemented incrementally to respect Mission Editor binding barriers.

The complete-fragment schema is exposed at
`dcs-me://schema/mission-fragment/0.1`. It accepts the data model used by a
DCS-SMS prefab (`meta`, `groups`, `statics`, `zones`, `drawings`, and
`triggers`) plus an explicit placement policy. The endpoint performs its
preflight and mutation inside the Mission Editor, so calling it requires DCS;
developing and running the repository test suites does not.

The high-level carrier request schema is exposed at
`dcs-me://schema/carrier-package/0.1`. Query `get_catalog` with
`kind=carrier_capabilities` first to inspect the open DCS installation's hulls,
entitlement, carrier-compatible aircraft, and escort types. `dry_run=true`
returns the compiled fragment and resolved wind-over-deck plan without changing
the mission. Auto module selection prefers Supercarrier when DCS reports it
installed and authorized and otherwise selects the coalition's free carrier.

The high-level airbase request schema is exposed at
`dcs-me://schema/airbase-start/0.1`. `create_airbase_start` requires an open
Mission Editor, checks the requested semantic revision and idempotency key,
preflights free compatible stands, invokes the native DCS-SMS construction
verb, then verifies group size, aircraft type, waypoint-zero airdrome linkage,
takeoff mode, and explicit parking IDs in a fresh snapshot. The default
`active_runway` parking strategy uses surface wind and runway designators to
rank free compatible stands by straight-line threshold distance, then verifies
the reassignment; `compact` preserves DCS-SMS's original nearby-group choice.
That distance is a taxi proxy, not a routed time guarantee. Fixed land
airfields use this endpoint; carrier deck slots use `setup_carrier_package`.
FARPs remain a separate future surface.

Trigger field values can refer to an earlier logical entity with
`"$ref:<logical_id>"`; the compiler substitutes its stable DCS name. A
reference to a named Triggered Action is resolved to DCS's current
`{groupId, taskNumber}` tuple immediately before the trigger action is added.
Route point `index` values in snapshots and every patch `waypoint_index` are
zero-based. Trigger rule indices and Triggered Action slots remain one-based.

Supported operations are:

- Existing entities: `adopt_entity` for exact group, unit, trigger, or zone names.
- Groups: `create_group`, `set_group_task`, `set_group_late_activation`,
  `remove_group`. Set `lead_logical_id` on `create_group` when later patches
  must address its first unit; the gateway binds the name returned by DCS.
- Units: `add_unit`, `set_unit_skill`, `set_unit_heading`,
  `set_unit_loadout`, `set_unit_payload`, `clear_unit_payload`, `remove_unit`.
  Payload consumables: `set_unit_fuel`, `set_unit_chaff`, `set_unit_flare`,
  and `set_unit_gun`.
- Routes: `add_waypoint`, `insert_waypoint`, `remove_waypoint`,
  `link_waypoint_airbase`.
- Waypoint behavior: `add_waypoint_task`, `add_waypoint_enroute_task`,
  `remove_waypoint_task`, `remove_waypoint_enroute_task`,
  `clear_waypoint_tasks`, `clear_waypoint_enroute_tasks`, and the typed
  `set_waypoint_option` operation. Task `fields` accept nested JSON-like arrays
  and records, including selectors such as `targetTypes: ["Air"]`.
- Group Triggered Actions: `add_triggered_task`, `replace_triggered_task`, and
  `remove_triggered_task`. These author task/en-route-task entries with stable
  names; use the task logical ID in an `a_ai_task` or `a_set_ai_task` trigger
  action field. Remove its referencing trigger action before removing the task.
- Environment: `set_mission_environment` for date/time, temperature, QNH,
  visibility, turbulence, wind at three levels, clouds, and dust.
- Triggers: `create_trigger`, `add_trigger_condition`, `add_trigger_action`,
  `remove_trigger_condition`, `remove_trigger_action`, and `remove_trigger`.
- Zones: `create_zone_circle`, `remove_zone`.

Route editing for a group created in the same patch requires a subsequent
patch so the Mission Editor can finish constructing its route state. Editing
or removing a unit created by `add_unit`, or a lead declared through
`create_group.lead_logical_id`, likewise requires a subsequent patch because
DCS allocates the final unit name in the operation result. Validation reports
these as explicit barrier errors before any mutation is dispatched.

### Preset-first aircraft workflow

For an armed aircraft, call `get_aircraft_loadouts` with `aircraft_type` and
`intended_task`. Prefer a DCS-authored preset that declares compatibility with
that task; when several match, choose the moderate, clearly named preset whose
stores fit the mission and expected range. Request `include_stations=true` for
the selected preset before any tuning. Apply it with `set_unit_loadout`, then
use `set_unit_payload` or `clear_unit_payload` only for an explicit requirement
the preset does not meet. A DCS named preset changes pylons only: compare the
unit's inherited fuel/chaff/flare/gun values with the returned airframe maximum
and countermeasure defaults, then set unsuitable values explicitly. Store-specific
fuze/program settings are not yet part of the gateway's constrained patch schema.

Loadout enumeration is a gateway-owned read-only adapter over DCS Mission
Editor `me_loadoututils`, not a formal DCS-SMS verb. It is therefore
version-sensitive, fails closed if the Mission Editor API changes, and has
been verified against the locally installed DCS build rather than claimed as
a stable Eagle Dynamics interface.

For AI behavior, first call `get_ai_task_catalog` for the actual group and task
kind, then `describe_ai_task`. `EngageGroup`/`AttackGroup` require a numeric
`groupId`; unit variants require `unitId`; `EngageTargetsInZone` requires
theatre-local `x` (north) and `y` (east). Use IDs from the current semantic
snapshot. A task name alone does not make a valid target assignment.

### AI fuel policies

Call `plan_fuel_policy` for every important AI flight. The default
`fuel_exhaustion_guard` is deliberately not a bingo policy: it disables native
RTB-on-bingo, waits until the first group member is below 2 percent, enables
unlimited fuel, and sends the group to a weapon-hold recovery leg. When a group
logical ID and zero-based recovery waypoint are supplied, the tool returns a
schema-valid operation list ready to splice into a mission patch.

Use `finite` when endurance and recovery are known to be safe,
`relief_rotation` for persistent services where a handover is worthwhile, and
`unlimited` only as an explicit abstraction. Unlimited fuel applies to the
whole group, so protected AI should not share a group with Player or Client
aircraft. `validate_miz_file` warns about high or zero guard thresholds,
player-group application, missing protection/recovery actions, wrong action
order, and native bingo behavior left enabled. The late protection mechanism
still needs a focused live DCS test; offline validation cannot prove AI runtime
behavior.

## Safety boundary

The underlying DCS-SMS bridge executes Lua in the full Mission Editor
environment. This server restricts generated code to a fixed batch dispatcher
and JSON/base64-serialized DCS-SMS verb calls. Keep it local, do not expose its
stdio through an unauthenticated network service, and leave External execution
disabled when it is not needed.

The offline validator and patch report do not use the DCS-SMS mailbox and do
not require DCS to be installed or running. They establish structural and
referential integrity only. They cannot prove that DCS will load the archive,
normalize it identically on save, or that runtime AI, sensors, triggers, and
weapons behave as intended; those remain explicit later acceptance layers.

## License

MIT. DCS-SMS is a separate project; its `tools/` tree is GPLv3.

## Build a Windows release

The release builder compiles the gateway, installs only locked production npm
dependencies into staging, downloads an official Node Windows distribution and
verifies its published SHA-256, copies the exact compatible DCS-SMS bridge
source and GPL text, inventories npm licenses, writes a per-file manifest, and
produces a ZIP plus checksum:

```powershell
npm run release:windows -- -DcsSmsSource C:\path\to\dcs-sms
```

Until the customized bridge 0.32.4 source is committed and published, releases
must be built from the verified local DCS-SMS worktree. The generated
`payload/bridge/SOURCE.md` records its base revision and whether the worktree
was dirty; the full corresponding bridge source is inside the same package.

Maintenance

ActivityMaintained
ResponsivenessNo issues