Your First Campaign

This page walks you through building the smallest campaign that loads: one Main entity, one Actor, and one Map. You package it, validate it, and boot it headless, using an example campaign as the worked reference.

Introduction

A campaign is a complete game definition written as data. You describe characters, maps, and rules in YAML files, package them as a .zip, and the Isometry binary loads them. No code changes are needed to make a game.

By the end of this page you will understand the shape every entity file shares, know which entities a campaign cannot do without, and have run the full build-validate-boot loop. An example campaign is referenced throughout so you can compare your work against a known-good campaign.

Prerequisites

Before you start, make sure you have:

The entity file shape

Every campaign is a folder of YAML files, and every file follows the same nesting: a type name, then one or more entity keys, then the fields for each entity. An entity is a pure-data record; there are exactly 33 entity types, and they hold data only, no game logic.

A minimal Main.yaml shows the shape at its smallest:

Main:
  demoMain:
    actor: heroActor
    map: demoMap

Read this as: a Main entity, keyed demoMain, with two fields. The values "heroActor" and "demoMap" are not actors and maps themselves; they are KeyRefs, that is, the keys of other entities the engine resolves at load time. Entity keys must be unique across the whole campaign, so a key used for an Actor cannot be reused for a Map.

Note: You may put several entities, even of different types, in one file. The loader merges every YAML file in the campaign into a single set of entities. An example campaign typically splits entities across files for clarity (Main.yaml, heroActor.yaml, map.yaml, and so on) but co-locates helpers, for example a map.yaml that also defines the Vector that the map's spawn point points to.

Main: the one required entity

Every campaign must contain exactly one Main entity. It defines the campaign's starting state, and only two of its fields are required:

Field Type Points to
actor KeyRef the player-controlled Actor
map KeyRef the starting Map

If Main is missing or duplicated, validation stops with an error such as Campaign requires exactly one Main entity (found 0).

Main also accepts two optional fields:

Because Main.actor and Main.map are the only required values anywhere in a minimal campaign, your next two files just need to exist under the keys Main names.

Actor: a character in the world

An Actor is a character, player-controlled or AI-driven. Every Actor field is optional, so a bare {} passes schema validation. In practice you give the player actor a name, a sprite, and a few stats.

Here are the core fields for a hero actor, trimmed to the essentials:

Actor:
  heroActor:
    name: Hero
    sprite: heroSprite
    base: 8
    speed: 1.0
    perception: 25
    salience: 1

Each field is doing one job:

Warning: speed is a Float field, so it must carry a decimal point. Write 1.0, not 1. Integer fields like base and perception accept whole-number floats such as 8.0, but a Float field rejects a bare integer.

A full hero also references skills, resources, measures, a menu, triggers, and timers through KeyRefArrays. You can add those later; see Create an actor for the complete field set.

Map: where play happens

A Map is a place actors occupy. Like Actor, all its fields are optional. A typical map.yaml references a tilemap, a spawn point, and more:

Map:
  demoMap:
    name: Demo Map
    tilemap: demoTileMap
    spawn: demoSpawn
Vector:
  demoSpawn:
    x: 128
    y: 16

Note that spawn is a KeyRef to a Vector entity, not a raw coordinate. The Vector, demoSpawn, is defined in the same file. This is the co-location pattern from earlier: the helper entity lives beside the entity that uses it.

For the smallest possible campaign, even tilemap and spawn may be omitted; a bare Map satisfies schema validation, and cross-reference validation only checks that any keys you do reference exist. To build a real, walkable map, see Build a map.

Package the campaign

Isometry loads a campaign from a .zip whose single root folder matches the campaign name. All asset paths inside the YAML are relative to that root.

Package your campaign folder with a standard zip command, run from the directory that contains it:

zip -r mycampaign.zip mycampaign/ -x "*.DS_Store"

This writes mycampaign.zip with the root folder inside the archive named mycampaign, and excludes .DS_Store files. Keep that root folder name consistent with the campaign name you load.

Validate and boot it

When a campaign loads, the validator runs in four phases:

  1. Schema — each entity has valid fields and types.
  2. Cross-reference — every KeyRef points to a real key, exactly one Main exists, and no keys are duplicated.
  3. Asset paths — referenced files exist (only when a ZIP archive is supplied).
  4. Dice expressions — any dice notation parses.

On success the engine logs Campaign validation passed.

The fastest check is a quick validate, which runs the validator and exits 0 on success or 1 on failure:

isometry --validate=mycampaign.zip

To run the full load pipeline headless, place the zip next to the engine binary and boot with --network=host:

isometry --campaign=mycampaign --network=host --port=5000 --username=p --secret=p --log-level=INFO

The campaign zip must sit in the same directory as the engine executable. Logs write to log.txt in that directory. A successful boot shows Campaign validation passed followed by Successfully created Entity lines.

Note: --network=none currently loads no campaign; it is a no-op. Use --network=host to exercise the load pipeline. See CLI reference for every flag.

If validation fails, the log names the entity and field at fault. Fix the YAML and rerun the boot command; you do not need to repackage unless you changed which files are in the zip.

See also