Skip to content
Version v0.35 ยท supported
[WIP] Pending copyedit and approval.

Composition

A profile is rarely one file. Amri builds the effective profile it runs by merging several parts together at load time: the parts you include, any overlay that layers on top, and the shared Lua libraries the commands draw on. The running engine sees a single merged result, not a live stack.

The parts

There are three ways a profile is assembled from more than profile.toml alone.

Includes pull extra command files into one profile. You keep the files under the profile's categories/ folder and list them in [profile]:

[profile]
name = "My Game"
include = ["combat.toml", "navigation.toml", "mining/basic.toml"]

Each included file holds [[command]] and [category.*] entries only, never its own [profile] section. An included file may itself include others. A cycle (a file that includes something that includes it back) is a load error, and an include cannot reach outside the profile folder.

Overlays layer one profile on top of another. An overlay is a profile that adds to or replaces entries in a base profile without editing the base. You declare the role with an [overlay] section:

[profile]
name = "elite_dangerous"

[overlay]
base = "fps_base"      # this profile layers on top of the fps_base profile

This is how a shared base profile for a game family carries the common commands while a per-title overlay changes only what differs. There is also a second kind, the engine's own system overlay, which is merged into every profile to provide built-in commands. Those are covered under System overrides.

Libraries are shared Lua files, loaded once, that every command in the profile can call. You name them in [profile.libraries] as name = "path.lua", where the path is relative to the profile's scripts/ folder:

[profile.libraries]
nav   = "lib/nav.lua"
radio = "lib/radio.lua"

Each library is loaded a single time when the profile loads and exposed to every command body as lib.<name>, so a script calls lib.radio.tune(121.5) without loading the file again on each command. A library file must return a Lua table.

Which definition wins

When two parts define a command with the same name, the more specific part wins. The order of specificity, from least to most:

  1. an included file,
  2. the base profile's own profile.toml,
  3. an overlay that layers on top,
  4. your local tweaks in user/overrides.toml.

So your profile.toml commands win over anything an included file defines, and a local override in user/ wins over everything else. Among includes, the include_conflict setting decides the tie: "overwrite" (the default) lets the later file win, "priority" lets the earlier file win, and "error" refuses the load when two includes name the same command.

The one exception is protected system commands. A base profile or overlay cannot silently replace a command that a system overlay defines. When it tries, the load is refused unless you have allowed that profile to override system commands in your own configuration. See System overrides for how that permission is granted.

Example

A profile that includes two command files, layers on a shared base, and shares one library:

[profile]
name = "elite_dangerous"
description = "Voice commands for Elite Dangerous."
include = ["combat.toml", "navigation.toml"]
include_conflict = "overwrite"

[overlay]
base = "space_sim_base"

[profile.libraries]
nav = "lib/nav.lua"

At load, Amri merges the base, the included files, this profile.toml, and any user/overrides.toml into one effective profile, then runs that.

Merging is a load-time step

The parts are combined once, when the profile becomes active. Nothing walks the stack while a command fires, so composition costs nothing at runtime. What a merged part is permitted to inject is still bounded by the profile's trust rules, covered under Trust.