# Forge World YAML contract

This is the complete format Forge World accepts. It is written so a person or a language
model can produce a bundle that validates on the first try. Give this page to your model
(paste it, or point it at `https://forgeworld.app/contract.md`) and ask it to write a bundle
for your idea. Then paste or upload the result at `https://forgeworld.app/validate` or
`https://forgeworld.app/render`.

Forge World is deterministic. The validator below is the same code that renders the mod: if
a bundle validates, it renders, and the same YAML always produces the same mod. Errors block
a render; warnings do not. Targets Stellaris 4.5.

## Bundle

A bundle is one `release.yaml` plus one or more event files:

```text
release.yaml
events/
  first_event.yaml
  second_event.yaml
```

- At most 50 files, each at most 100 KB. `.yml` works too.
- File names don't matter; events are found by their `slug`.
- On the Render page you can also paste a single event; Forge World wraps it in a draft
  `release.yaml` for you.

## release.yaml

All five fields are required and must be non-empty.

| Field | Meaning |
|---|---|
| `code` | Mod identifier. Lowercase letters, digits and underscores only, e.g. `drifting_museum`. |
| `name` | Mod name shown in the launcher. |
| `month` | Release month, e.g. `'2026-10'` (quote it). |
| `status` | Free text, e.g. `draft`. |
| `notes` | Free text, one line is fine. |

## Event files

Each event file is one event: a mapping with these fields.

| Field | Required | Meaning |
|---|---|---|
| `slug` | yes | Unique id within the bundle, lowercase with underscores. Other events refer to it. |
| `kind` | yes | `standalone`, `anomaly` or `site` (see below). |
| `name` | yes | Event name. |
| `interaction_events` | yes | List of interactions (the popups). At least one. |
| `default_trigger_type` | no | `gatekeeper_pool` (fires on its own over time; the default) or `triggered_only` (fires only from another event's `trigger_event`). |
| `notes` | no | Free text for yourself. |
| `site_difficulty` | no | Sites only: 1 to 10, default 3. Each chapter is one step harder. |

### Kinds

- **`standalone`**: an empire event. It fires on its own unless it is `triggered_only`. Every
  event fires at most once per game.
- **`anomaly`**: a science ship discovers it while surveying. Needs at least one interaction
  with `role: anomaly_success`; an optional `role: anomaly_fail` interaction plays on failure.
- **`site`**: an archaeological dig site in chapters. Every interaction needs `chapter_index`
  (1, 2, 3...), and the site has as many stages as interactions. Needs the Ancient Relics
  story pack in game.

## Interactions

Each item in `interaction_events`:

| Field | Required | Meaning |
|---|---|---|
| `role` | yes | A name for this interaction. `trigger_event` links use it (`slug/role`). Anomalies use `anomaly_success` and `anomaly_fail`; sites conventionally use `site_chapter`. |
| `title` | yes | Popup title. |
| `description` | yes | Popup body. Plain text; use `>-` for long text. |
| `options` | yes | Non-empty list of buttons. A popup with no options can't be closed, so it's an error. |
| `chapter_index` | sites | Chapter number, starting at 1. |
| `picture_hint` | no | An event picture such as `GFX_evt_derelict_interior`, `GFX_evt_archaeological_dig`, `GFX_evt_alien_ruins`, `GFX_evt_star_chart`, `GFX_evt_ancient_databank`. Unknown names fall back to a default picture. |

## Options

| Field | Required | Meaning |
|---|---|---|
| `opt_key` | yes | Short id, e.g. `A`, `B`. |
| `text` | yes | Button label. |
| `effects` | no | List of effects (below). An option with no effects just closes the popup. |

## Effects

Every effect has a `category` and that category's required fields. Any other category is an
error. Amounts are what the player gets: negative numbers are costs.

### resource_gain

Add or remove a flat amount.

- `resource` (required): one of `energy`, `minerals`, `food`, `alloys`, `consumer_goods`,
  `unity`, `influence`, `trade`, `physics_research`, `society_research`,
  `engineering_research` (or just `physics`, `society`, `engineering`), `volatile_motes`,
  `exotic_gases`, `rare_crystals`, `sr_living_metal`, `sr_zro`, `sr_dark_matter`, `nanites`,
  `minor_artifacts`, `astral_threads`.
- `amount` (required): a number.

### resource_gain_scaled

Add or remove a share of the empire's current stockpile, so the cost or reward matters at
any stage of the game.

- `resource` (required): as above.
- `percent_of_stockpile` (required): a fraction, e.g. `-0.08` for 8% of the stockpile.
- `min_amount`, `max_amount` (optional): clamp the result. For a loss, `min_amount` is the
  larger loss: `min_amount: -800, max_amount: -100`.

### research_progress

Research boost in one field, sized to the empire's research income.

- `tech_area` (required): `physics`, `society` or `engineering`.
- `progress` (required): a fraction such as `0.10` (about a year of that field's output,
  capped).

### add_tech_progress

- `tech` (required): a technology id, e.g. `tech_destroyers`.
- `progress` (required): fraction of that tech, e.g. `0.25`.

### give_technology / add_research_option

- `tech` (required): a technology id. `give_technology` grants it outright;
  `add_research_option` offers it as a research option.

### modifier_add

A timed empire modifier.

- `modifier_name` (required): short id, e.g. `archive_debt`.
- `duration_days` (required): days; 360 is one year.
- `modifier_description` (recommended): what players read on hover. Missing it is a warning.
- `modifier_stat` and `modifier_value` (optional, together): the exact effect, e.g.
  `modifier_stat: country_unity_produces_mult` with `modifier_value: 0.1` for +10% unity.
  Without them, Forge World picks a stat from words in the name and description.

### planet_modifier_add

A timed modifier on the empire's capital planet.

- `modifier_name`, `duration_days` (required), `modifier_description` (recommended), as above.
- `modifier_stat` and `modifier_value` (required): a planet-scope stat starting with
  `planet_` or `pop_`, e.g. `planet_stability_add` with `5`.

### add_artifacts

- `amount` (required): minor artifacts to grant.

### add_deposit

- `deposit_type` (required): a deposit id such as `d_minerals_3`. Anomaly events only: the
  deposit lands on the surveyed planet.

### trigger_event

Fire another interaction from this bundle, now or later. This is how follow-ups work.

- `event_key` (required): `slug/role` of the target interaction, e.g.
  `strange_signal_followup/main`. The slug must exist and the role must be unique in that
  event. Raw event ids are not accepted; ids are assigned at render time.
- `days_delay` (optional): days to wait, 0 or more.

The target event should be `triggered_only`. Follow-up events are conventionally named
`<parent_slug>_followup` (or `_followup_b` and so on), and an event whose slug ends that way
must be `triggered_only`. A `triggered_only` event must be `kind: standalone`.

`event_key: PLACEHOLDER` also works: it links to the parent's next `<parent_slug>_followup*`
event, in order.

### ethic_shift

- `ethic_key` (required): `ethic_authoritarian`, `ethic_egalitarian`, `ethic_xenophobe`,
  `ethic_xenophile`, `ethic_militarist`, `ethic_pacifist`, `ethic_spiritualist` or
  `ethic_materialist`, or the `ethic_fanatic_` form of any of them.

### trait_leader_add

- `trait_key` (required): a leader trait id starting with `leader_trait_`, e.g.
  `leader_trait_spark_of_genius`.
- `leader` (optional): `ruler` (default) or `scientist`. `scientist` works only in anomaly
  and site events, where a science ship is involved.

### spawn_anomaly

- `anomaly` (required): the slug of an `anomaly` event in this bundle. It appears on a free
  planet in a system the empire owns.

### opinion_change

Every empire you have met changes its opinion of you. The change fades over about ten years.

- `opinion_name` (required): short id, lowercase with underscores, e.g. `museum_thieves`.
- `opinion` (required): a non-zero number from -200 to 200, e.g. `-25`.
- `opinion_label` (optional): what the diplomacy screen shows, e.g. `Stole from the museum`.
  Defaults to the name.

### create_project

A research project at your capital. When it completes, it fires an interaction from the bundle.

- `project_name` (required): short id, lowercase with underscores.
- `tech_area` (required): `physics`, `society` or `engineering`.
- `cost` (required): research points, e.g. `1500`.
- `on_complete` (required): `slug/role` of the interaction to fire. It must be a `standalone`
  event, usually `triggered_only`.
- `project_title` (optional) and `project_description` (recommended): what the player reads.
  Missing the description is a warning.

### spawn_fleet

New ships of one class join the empire at its capital, built from one of its own designs.

- `ship_class` (required): `corvette`, `destroyer`, `cruiser`, `battleship`, `science` or
  `constructor`. Destroyers and up need the empire to have a design of that class.
- `count` (optional): 1 to 5, default 1.

## Warnings

These don't block a render, but you should read them:

- `modifier_add` or `planet_modifier_add` without `modifier_description`.
- `create_project` without `project_description`.
- `days_delay` over 3650 days: the follow-up may never fire.

## Complete example

A two-event bundle: a standalone event whose first option schedules a follow-up. It
validates and renders as is.

File `release.yaml`:
```yaml
code: drifting_museum
name: The Drifting Museum
month: '2026-10'
status: draft
notes: Example bundle from the Forge World contract
```

File `events/accession_notice.yaml`:
```yaml
slug: accession_notice
kind: standalone
name: Accession Notice
interaction_events:
  - role: main
    title: Accession Notice
    description: >-
      A courier drone delivers a museum receipt for our capital's central plaza,
      "donated" twelve years from now. The signature is the current governor's.
    picture_hint: GFX_evt_ancient_databank
    options:
      - opt_key: A
        text: Investigate the receipt.
        effects:
          - category: resource_gain_scaled
            resource: energy
            percent_of_stockpile: -0.05
            min_amount: -300
            max_amount: -25
          - category: trigger_event
            event_key: accession_notice_followup/main
            days_delay: 90
      - opt_key: B
        text: File it with the other impossible paperwork.
        effects:
          - category: modifier_add
            modifier_name: filed_and_forgotten
            modifier_description: An unexplained receipt sits in the archive, unread.
            modifier_stat: country_unity_produces_mult
            modifier_value: 0.05
            duration_days: 360
```

File `events/accession_notice_followup.yaml`:
```yaml
slug: accession_notice_followup
kind: standalone
name: Provenance
default_trigger_type: triggered_only
interaction_events:
  - role: main
    title: Provenance
    description: >-
      The courier's flight log leads to a museum station adrift at the edge of our
      space. Its curators have already prepared a plaque with our name on it.
    options:
      - opt_key: A
        text: Accept the plaque.
        effects:
          - category: add_artifacts
            amount: 2
          - category: research_progress
            tech_area: society
            progress: 0.10
```
