Skip to content

Assets pipeline

How does X4 go from an XML file on disk to a 3D ship flying in space? The assets pipeline is the chain of file types that wire definitions together β€” macro XML defines properties, component XML defines geometry, index files map names to file paths, the engine loads on-demand. This overview maps the chain.

For the macro-level details see Macro (data layer).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Index files (index/) β”‚
β”‚ - macros.xml: name β†’ file path mapping β”‚
β”‚ - components.xml: same for components β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ engine resolves at load
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Macro file (.xml) β”‚
β”‚ - <macro name="X" class="Y"> β”‚
β”‚ - <component ref="..."/> β”‚
β”‚ - <properties>...</properties> β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ references
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Component file (.xml) β”‚
β”‚ - 3D mesh / collision data refs β”‚
β”‚ - connection points β”‚
β”‚ - sub-component slots β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ references
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Mesh / texture / animation data β”‚
β”‚ - Binary asset files β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The index/ directory has two files:

FileMaps
index/macros.xmlMacro name β†’ file path
index/components.xmlComponent name β†’ file path

These are giant lookup tables. Vanilla index/macros.xml has thousands of entries:

<entry name="ship_arg_l_destroyer_01_a_macro"
value="assets/units/size_l/macros/ship_arg_l_destroyer_01_a_macro"/>
<entry name="station_arg_shy_1_macro"
value="assets/structures/argon/macros/station_arg_shy_1_macro"/>

When MD calls <create_object macro="macro.ship_arg_l_destroyer_01_a_macro">, the engine:

  1. Looks up ship_arg_l_destroyer_01_a_macro in index/macros.xml
  2. Finds the file path
  3. Loads the macro XML
  4. Resolves the macro’s <component ref="..."> against index/components.xml
  5. Loads the component XML
  6. Instantiates the runtime object

Critical: if index/macros.xml doesn’t have an entry, the engine can’t find the macro. Adding a new macro file isn’t enough β€” you must also add its index entry.

A macro file defines a runtime template β€” properties, class, default loadout. See Macro (data layer) for the schema.

Key relationship: a macro references its component:

<macro name="ship_arg_l_destroyer_01_a_macro"
class="ship_l">
<component ref="ship_arg_l_destroyer_01_a"/>
<properties>...</properties>
</macro>

The <component ref="..."> is the link from β€œdesign” (macro) to β€œthing in the world” (component).

A component file defines the physical structure β€” meshes, collision, connections to sub-components:

<component name="ship_arg_l_destroyer_01_a">
<source geometry="..." collision="..."/>
<connections>
<connection name="con_weapon_01" type="weapon">
...
</connection>
<connection name="con_engine_01" type="engine">
...
</connection>
</connections>
</component>

Components are about geometry + connection topology. Properties (hull, speed, cargo) live on macros, not components.

Components have connection points that map to sub-components:

ship_arg_l_destroyer_01_a (main component)
β”œβ”€β”€ con_weapon_01 β†’ weapon component (e.g. turret_01_a)
β”œβ”€β”€ con_weapon_02 β†’ weapon component
β”œβ”€β”€ con_engine_01 β†’ engine component
β”œβ”€β”€ con_shield_01 β†’ shield generator component
└── con_dock_01 β†’ dockingbay component

Each connection has a type tag (weapon, engine, shield). Sub-components must have a compatible connection on their end. The engine matches them at instantiation time.

Sub-components also have macros β€” turret_arg_l_beam_01_mk2_macro references the turret component. This is how the loadout system works: a loadout assigns specific turret/engine/shield macros to specific connection slots:

ship_arg_l_destroyer_01_a runtime instance
β”œβ”€β”€ slot con_weapon_01: turret_arg_l_beam_01_mk2_macro
β”œβ”€β”€ slot con_engine_01: engine_arg_l_advance_01_mk1_macro
└── slot con_shield_01: shield_arg_l_standard_01_mk2_macro

This is the loadout system. Different loadouts = same ship with different equipment. See Loadout for runtime details.

Vanilla organises assets by category:

assets/
β”œβ”€β”€ units/ ← ships
β”‚ β”œβ”€β”€ size_xs/macros/
β”‚ β”œβ”€β”€ size_s/macros/
β”‚ β”œβ”€β”€ size_m/macros/
β”‚ β”œβ”€β”€ size_l/macros/
β”‚ └── size_xl/macros/
β”œβ”€β”€ structures/ ← stations + modules
β”‚ β”œβ”€β”€ argon/macros/
β”‚ β”œβ”€β”€ paranid/macros/
β”‚ β”œβ”€β”€ teladi/macros/
β”‚ β”œβ”€β”€ modules/macros/
β”‚ └── ...
β”œβ”€β”€ equipment/ ← turrets, engines, shields
β”‚ β”œβ”€β”€ turrets/macros/
β”‚ β”œβ”€β”€ engines/macros/
β”‚ β”œβ”€β”€ shields/macros/
β”‚ └── ...
β”œβ”€β”€ fx/ ← effects
β”‚ └── macros/
└── ...

Files in each macros/ subdirectory are macros; component files live adjacent (often in components/ subdirectories).

Vanilla uses _macro suffix on macro names: ship_X_macro, module_Y_macro. Engine treats this as convention β€” macro.ship_X_macro is the lookup. Don’t omit the suffix.

The corresponding component drops the suffix: ship_X is the component name for macro ship_X_macro. So macro β†’ component is _macro removal.

Each asset directory typically has a catalog.xml that summarises its contents. This is for engine discovery / mod compatibility β€” listing which macros / components are present, sometimes their classes.

Modders extending an asset directory usually don’t touch catalog.xml β€” the index files (index/macros.xml) are what the engine actually uses for lookups.

To add a new ship ship_mlog_my_destroyer:

  1. Create component file ship_mlog_my_destroyer.xml in an assets/.../components/ directory
  2. Create macro file ship_mlog_my_destroyer_macro.xml referencing the component
  3. Add entries to index/macros.xml (macro path) and index/components.xml (component path)
  4. (Optional) Create loadout files for default equipment
  5. (Optional) Reference from god.xml for initial seeding

Without step 3, the engine can’t find your files.

Two mods adding the same macro name overwrite each other’s index entries. Use mlog_ prefix or other namespace convention to avoid collisions.

A new ship doesn’t always need a new component. If you’re re-skinning, you can:

  • Reuse an existing component
  • Create a new macro with different properties
  • Point the macro’s <component ref> at the existing component

This is how vanilla creates β€œ_a”, β€œ_b”, β€œ_c” variants of the same ship.

DLC content lives under separate paths (extensions/ego_dlc_terran/assets/...). Index files in extensions add to the same global namespace. Modders don’t need to worry about this directly β€” the engine merges all extensions’ indices.

Editing a macro file in a mod changes ALL future and existing instances of that macro. For per-instance changes, use MD <set_value> on the runtime object.

  • Macro (data layer) β€” macro file schema
  • Loadout β€” runtime equipment from macros
  • Ship β€” runtime instance of ship macro
  • Module β€” runtime instance of module macro