Create / Modding reference / Mod files

Mod files

A Nova Protocol mod is a folder that contains one bundle manifest, one or more content files, and any art or audio that the mod owns. Content files can define seven kinds of reusable item: campaigns, scenarios, ship sections, whole ships, ship skin styles, training lessons, and UI themes.

Use this page to choose the right file. Then open the detailed reference for the item you want to author.

Folder structure

A small mod can use this layout:

my-mod/
|- my-mod.bundle.ron
|- campaign.content.ron
|- scenarios.content.ron
|- sections.content.ron
|- icon.png
|- screenshots/
|  `- mission.png
`- textures/
   `- skybox.png

Only the bundle manifest has a fixed role. The content filenames and folder layout are your choice, but every content file must end in .content.ron and be listed by the manifest.

The manifest filename must use the mod id as its stem: my-mod.bundle.ron. Do not name it only bundle.ron.

The bundle manifest

The bundle tells the loader which files belong to the mod:

(
    content: [
        "campaign.content.ron",
        "scenarios.content.ron",
        "sections.content.ron",
    ],
    resources: [
        "icon.png",
        "screenshots/mission.png",
        "textures/skybox.png",
    ],
    meta: (
        name: "My Mod",
        description: "A short campaign with custom ship parts.",
        author: "Your Name",
        version: "0.1.0",
        dependencies: [],
        icon: Some("icon.png"),
        screenshots: ["screenshots/mission.png"],
    ),
)
field type default meaning
content list of paths required Content files loaded from this folder. Paths are relative to the manifest.
resources list of paths [] Images, models, sounds, and other binary files owned by the mod.
meta.name string empty Player-facing mod name. Required for portal publishing.
meta.description string empty Player-facing summary.
meta.author string empty Author or team name.
meta.version string empty Release identifier. Required for portal publishing; change it for each release.
meta.dependencies list of mod ids [] Other mods whose content and dep://<id>/ resources this mod uses. Base is always available.
meta.icon Option path None Mod icon relative to this folder. Use Some("icon.png").
meta.screenshots list of paths [] Mod screenshots relative to this folder.
new_game_scenario Option scenario id None Base-game-only setting. The game warns and ignores it in ordinary mods.

resources lists files, not folders. A .meta sidecar next to a listed image travels with that image automatically and is not listed separately.

The installed catalog

assets/mods.catalog.ron lists every mod the build ships, in load order, and is the only index the loader reads: there is no directory scan, so the web build works from the same file. Each entry is a thin pointer; the mod's metadata stays in its own bundle's meta block.

field type default meaning
id string required The enable key and overlay namespace, and the <id> in another mod's dep://<id>/ refs.
bundle path required The mod's *.bundle.ron, relative to the assets root.
base bool false The base game: enabled by default and locked on in the Mods menu.

Every cataloged mod gets a row in the Mods menu: there is no way to install content the player can neither see nor switch off. A tooling or test rig belongs in the example or test that owns it, not in the catalog.

A fresh install enables base and nothing else. Your mod ships installed and switched off, and the player turns it on from its own row.

Only base is mandatory. The game loads every other cataloged mod on its own, beside the boot load: if one of them will not parse or its content will not load, that mod is switched off, written off in the saved enabled set, and named in one MODS DISABLED notice on the main menu. The files stay installed, and the Mods screen is where the player updates, removes or re-enables it. So a mod you ship broken costs that mod, not the player's game - but nothing tells them more than the loader's own first line, which is what content lint is for.

The load also runs the section checks content lint runs, on every section a mod carries, used or not. One failing section switches off the WHOLE mod, and the notice names the section and the first fault. Every enabled mod that depends on it is switched off with it, because its ships name sections that are no longer there. A mod that overrides a base section with a broken one is switched off, and the base section stays.

During a scenario, a mod that fails to load, fails these checks, or depends on a mod that was switched off (after an update or a download, for example) is not switched off: the game stops with an error that names the mod.

The full packaging, catalog, local installation, and publishing flow is in Publish a mod.

Balance acknowledgments

content lint grades every combat scenario for fairness. A close-spawn warning says a hostile arrives inside its own weapon envelope of the player spawn. When that is the point - a boss entrance, a scripted ambush - declare it in a balance_acks.ron beside the manifest. The linter reads the file from the bundle it lints, so the justification travels with the mod:

[
    (
        scenario: "my_first_mission",
        hostile: "boss_1",
        kind: "close-spawn",
        reason: "The finale entrance. It telegraphs with an 8s engage_delay and a warning line.",
        task: "2026-08-16",
    ),
]

The file is optional and is not listed in content. An acknowledged finding still prints, tagged ACK with its reason, but stops counting as an open warning.

field meaning
scenario The scenario id the finding sits in.
hostile The scenario object id of the hostile the finding names.
kind close-spawn. A spawned-dead finding is an ERROR and can never be acknowledged.
reason Why this one is intended. Written for the next reader, not for the linter.
task Whatever records the decision - a ticket id, a date.

An ack that matches no live finding is STALE and fails the lint: once the content is rebalanced, prune the entry.

Content files

Every *.content.ron file is a RON list. One file may contain any mix of the seven item kinds:

[
    Campaign((
        id: "my_campaign",
        name: "My Campaign",
        scenarios: ["my_first_mission"],
    )),
    Scenario((
        id: "my_first_mission",
        name: "First Mission",
        description: "Clear the shipping lane.",
        cubemap: "self://textures/skybox.png",
        events: [],
    )),
    Section((
        base: (
            id: "my_mod_hull",
            name: "My Hull",
            description: "A custom armor block.",
            health: 150.0,
        ),
        kind: Hull((
            render_mesh: Some("dep://base/gltf/hull-01.glb#Scene0"),
        )),
    )),
    Ship((
        id: "my_corvette",
        name: "My Corvette",
        hull: (sections: []),
    )),
    Style((
        id: "my_look",
        name: "My Look",
        palette: (
            top: (color: Srgba((red: 0.4, green: 0.4, blue: 0.42, alpha: 1.0)), roughness: 0.8, metallic: 0.2),
            wall: (color: Srgba((red: 0.1, green: 0.1, blue: 0.12, alpha: 1.0)), roughness: 0.9, metallic: 0.2),
        ),
        fixtures: [],
    )),
    UiTheme((
        id: "my-mod/amber",
        name: "Amber CRT",
        inherit: Some("base/phosphor"),
        palette: {
            "primary": "#ffb84a",
        },
        metrics: None,
        roles: (),
    )),
]

Splitting these into campaign.content.ron, scenarios.content.ron, and sections.content.ron is a readability convention, not a loader requirement. Large mods can use one scenario per file and list all of them in content.

The seven content chapters

  • A campaign orders scenarios for the Scenarios menu.
  • A scenario defines a playable mission, a menu backdrop or a handbook practice range - see roles. Events, filters, actions, objects, and expressions belong to scenario scripting.
  • A section defines a reusable hull, thruster, controller, turret, or torpedo bay.
  • A ship defines a whole hull - its section layout and cladding - that any scenario can spawn by id.
  • A style defines the look a ship's derived cladding wears: the two plate surfaces, and the destructible decoration scattered over them.
  • A lesson is one screen of the in-game training handbook: a demonstration, a few lines, the actions it names, and sometimes a focused range to fly.
  • A UI theme is the look the whole interface paints in: the palette, the metrics, and the paint of every control in every state. The player picks one in Settings.

Paths and dependencies

Content uses explicit asset schemes:

  • self://textures/skybox.png reads a file from this mod's resources list.
  • dep://base/gltf/hull-01.glb#Scene0 uses a base-game resource.
  • dep://art-pack/models/station.glb#Scene0 uses a resource from a mod listed in meta.dependencies.

A plain path without self:// or dep:// is not a valid asset reference. See the base content catalog for reusable base ids and assets.

Overlay behavior

Content merges by item id:

  • A new id adds a campaign, scenario, section, ship, style, lesson, or theme.
  • An id that already exists replaces that whole item.
  • A duplicate id inside one bundle is a conflict; the first item is kept.

For sections, the key is base.id. For campaigns, scenarios, ships, styles, lessons, and UI themes, the key is id. Prefix new ids with your mod id to avoid accidental collisions.