Skip to content

MD vs Aiscript

X4 has two scripting languages: MD (Mission Director) and Aiscript. They cover different domains, but the boundary isn’t obvious from documentation. This page is the decision guide.

For the language references see MD Framework and Aiscript Framework.

You’re writingUse
Mission content (offers, acceptance, completion)MD
Story content (cutscenes, characters)MD
Faction logic (economy, goals, expansion)MD
Faction relations + diplomacyMD
UI menus + HUD widgetsLua (see UI/Lua)
Custom NPC ship behaviourAiscript
New ship orders (Patrol, Attack, Move, etc.)Aiscript
Reactive ship reflexes (under attack, scanned)Aiscript (Interrupt)

The boundary: MD orchestrates THE GAME; Aiscript governs INDIVIDUAL SHIPS.

You need to script something.
Does it involve a specific NPC ship's behaviour?
(e.g. "if X happens, ship does Y")
┌─────────────────┴─────────────────┐
│ YES NO
↓ ↓
AISCRIPT Does it involve game state
across factions / story / missions?
┌───────────┴───────────┐
│ YES NO
↓ ↓
MD Is it about UI
visible to player?
┌──────────┴──────────┐
│ YES NO
↓ ↓
LUA + SN APIs Probably MD
(or you're solving
the wrong problem)
ConcernMDAiscript
TriggerCue conditions (events + checks)Order conditions OR interrupt handlers
LifecyclePer-cue (instantiate / queue / state machine)Per-order on a ship
State$variables per cue, namespace.$X, global.$X$variables per order instance
PersistenceAll vars saved automaticallyVars saved with the ship
MD ↔ aiscript bridge<signal_objects>, <create_order>signal_cue from <on_finish>
PerformanceTick-based; per-cue overheadPer-frame for active orders + interrupts
Where it runsGame-levelPer-ship
Vanilla file extension.xml (in md/).xml (in aiscripts/)
Root element<mdscript><aiscript>

Use MD. Create gm_X.xml mirroring vanilla Mission framework patterns. The mission cue:

  • Offers via <create_offer> from MD
  • Listens for player accept
  • Spawns objects via <create_ship> / <create_object>
  • Awards rewards via LIB_Reward_Balancing

Mission cues drive everything. Aiscript orders are subsidiary — they make NPC ships do specific actions during the mission.

”My mod adds a new NPC ship behaviour”

Section titled “”My mod adds a new NPC ship behaviour””

Use Aiscript. Create order.X.xml. The order:

  • Defines its <params> (player-configurable settings)
  • Implements <actions> (what the ship does)
  • Subscribes to relevant <interrupts>
  • Uses <attention> blocks to scale cost-of-execution

See Order definition.

Probably MD. Listen for player events:

<event_player_signalled/>
<event_player_trade_completed/>
<event_object_destroyed/>

MD is the right place for “watch and react” patterns. Don’t try to wire this through aiscript — aiscript is per-ship, not per-event.

Use MD (mostly):

  • Faction declaration in libraries/factions.xml (data, not script)
  • Faction logic via MD cues (registered in FactionGoals)
  • Initial seeding via god.xml (data)

Some aiscript may be needed if your faction has unique ship behaviour, but the bulk is MD.

See New faction recipe for the data side.

Use Lua + SN APIs. See UI/Lua and the UI Lua framework overview.

For the data the menu displays, use MD (state lives in MD vars; menu reads via event bus).

”My mod tracks faction state across the game”

Section titled “”My mod tracks faction state across the game””

Use MD. Variables like global.$mlog_state persist across the session. Cues read and update them.

Aiscript can’t do this well — aiscript state is per-ship.

<create_order id="'MyCustomOrder'" object="$ship">
<param name="param1" value="$value"/>
</create_order>

The order is queued; aiscript runs it. See Order definition.

In the order’s <on_finish>:

<signal_objects object="this.$mission_cue"
param="'order_done'"/>

In MD, listen:

<cue name="WatchOrderDone" instantiate="true">
<conditions>
<event_object_signalled
object="this.$mission_cue"
param="'order_done'"/>
</conditions>
<actions>...</actions>
</cue>

This is the canonical mission ↔ ship bridge.

”I’ll use aiscript because it runs faster”

Section titled “”I’ll use aiscript because it runs faster””

The performance difference is not material for most use cases. Use the framework that fits the domain. Misusing aiscript for game-level logic results in convoluted code.

”I’ll use MD because it’s easier to read”

Section titled “”I’ll use MD because it’s easier to read””

For per-ship behaviour, MD is the WRONG tool. You’ll fight the framework. Use aiscript and accept the learning curve.

You can’t. MD lives in md/, aiscript in aiscripts/. Different XML roots, different schemas, different lifecycle. Pick one.

”I’ll do everything in MD via create_order”

Section titled “”I’ll do everything in MD via create_order””

You can drive ship behaviour purely from MD with continuous <create_order> calls — but the overhead is high, and you’re recreating aiscript’s job poorly. If you need ship behaviour, write an aiscript order.

Default to MD unless you’re CERTAIN it’s per-ship behaviour. MD is more flexible; aiscript is more specialised.

If your need is “make ships do X under condition Y” — aiscript.
If your need is “make THE GAME do X under condition Y” — MD.