Last modified: January 24, 2026

This article is written in: πŸ‡ΊπŸ‡Έ

Mission Framework Documentation

Overview

The Mission Framework provides a formal authoring layer for creating structured gameplay experiences in Standard of Iron. It separates playable maps from mission logic, allowing designers to create reusable missions and organize them into campaigns.

Architecture

Three-Tier Structure

assets/
  β”œβ”€β”€ maps/          # Playable level geometry + terrain
  β”‚   β”œβ”€β”€ map_forest.json
  β”‚   β”œβ”€β”€ map_mountain.json
  β”‚   └── map_rivers.json
  β”œβ”€β”€ missions/      # Gameplay rules that reference maps
  β”‚   β”œβ”€β”€ defend_outpost.json
  β”‚   └── forest_ambush.json
  └── campaigns/     # Ordered mission collections
      └── second_punic_war.json

Components

Mission Configuration

File Structure

{
  "id": "defend_outpost",
  "title": "Defend the Outpost",
  "summary": "Hold your position against waves of enemy attacks",
  "map_path": ":/assets/maps/map_forest.json",
  
  "player_setup": { ... },
  "ai_setups": [ ... ],
  "victory_conditions": [ ... ],
  "defeat_conditions": [ ... ],
  "events": [ ... ]
}

Player Setup

Defines the human player's starting configuration:

"player_setup": {
  "nation": "roman_republic",
  "faction": "roman",
  "color": "red",
  "starting_units": [
    {
      "type": "spearman",
      "count": 10,
      "position": {"x": 60, "z": 60}
    }
  ],
  "starting_buildings": [
    {
      "type": "barracks",
      "position": {"x": 60, "z": 60},
      "max_population": 200
    }
  ],
  "starting_resources": {
    "gold": 1000,
    "food": 500
  }
}

AI Setup

Defines AI opponents with personality and behavior:

"ai_setups": [
  {
    "id": "enemy_1",
    "nation": "carthage",
    "faction": "carthaginian",
    "color": "blue",
    "difficulty": "medium",
    "team_id": 1,
    "personality": {
      "aggression": 0.7,
      "defense": 0.3,
      "harassment": 0.5
    },
    "waves": [
      {
        "timing": 120.0,
        "composition": [
          {"type": "swordsman", "count": 8},
          {"type": "archer", "count": 4}
        ],
        "entry_point": {"x": 190, "z": 190}
      }
    ]
  }
]

Team ID (Optional)

AI opponents can be assigned to teams using the team_id field. AI players with the same team_id will be allied and won't attack each other. If team_id is not specified, each AI player will be on their own team (enemies to all other AI players).

Example with allied AI opponents:

"ai_setups": [
  {
    "id": "roman_legion_1",
    "nation": "roman_republic",
    "team_id": 1,
    ...
  },
  {
    "id": "roman_legion_2",
    "nation": "roman_republic",
    "team_id": 1,
    ...
  }
]

Victory Conditions

Supported victory condition types:

Example:

"victory_conditions": [
  {
    "type": "survive_duration",
    "duration": 600.0,
    "description": "Survive for 10 minutes"
  }
]

Defeat Conditions

Supported defeat condition types:

Example:

"defeat_conditions": [
  {
    "type": "lose_structure",
    "structure_type": "barracks",
    "description": "Do not lose your barracks"
  }
]

Events

Timed or state-based triggers for dynamic gameplay:

"events": [
  {
    "trigger": {
      "type": "timer",
      "time": 300.0
    },
    "actions": [
      {
        "type": "show_message",
        "text": "Reinforcements approaching!"
      }
    ]
  }
]

Campaign Configuration

File Structure

{
  "id": "second_punic_war",
  "title": "Second Punic War",
  "description": "Campaign across the Mediterranean",
  "missions": [
    {
      "mission_id": "forest_ambush",
      "order_index": 0,
      "intro_text": "Your first task...",
      "outro_text": "Well done!",
      "difficulty_modifier": 1.0
    }
  ]
}

Mission Ordering

Content Validation

Validator CLI

Run content validation:

# Via CMake
cmake --build build --target validate-content

# Via Makefile
make validate-content

# Direct execution
./build/bin/content_validator assets

Validation Checks

The validator performs:

  1. JSON Schema Validation
  2. Proper structure and required fields
  3. Correct data types

  4. Asset Reference Validation

  5. Referenced maps exist
  6. Campaign missions reference valid mission files

  7. Campaign Validation

  8. Mission order indices are contiguous
  9. Starts at 0 or 1

  10. Cross-Reference Checks

  11. Units, buildings, and entity types are valid
  12. No circular dependencies

Build Integration

Add to your build process:

# Make validation part of the build
add_dependencies(standard_of_iron validate-content)

When uncommented, invalid content will fail the build.

Usage in Code

Loading Missions

#include "game/map/mission_loader.h"

Game::Mission::MissionDefinition mission;
QString error;

if (!Game::Mission::MissionLoader::loadFromJsonFile(
    ":/assets/missions/defend_outpost.json", 
    mission, 
    &error)) {
  qWarning() << "Failed to load mission:" << error;
  return;
}

// Use mission data
qInfo() << "Loaded mission:" << mission.title;

Loading Campaigns

#include "game/map/campaign_loader.h"

Game::Campaign::CampaignDefinition campaign;
QString error;

if (!Game::Campaign::CampaignLoader::loadFromJsonFile(
    ":/assets/campaigns/second_punic_war.json",
    campaign,
    &error)) {
  qWarning() << "Failed to load campaign:" << error;
  return;
}

// Iterate missions
for (const auto& mission : campaign.missions) {
  qInfo() << "Mission" << mission.order_index << ":" << mission.mission_id;
}

Starting a Mission

// In GameEngine
void GameEngine::start_campaign_mission(const QString &mission_path) {
  // mission_path format: "campaign_id/mission_id"
  // Example: "second_punic_war/defend_outpost"
  
  // Mission loader parses JSON and configures:
  // - Player and AI setups
  // - Victory/defeat conditions
  // - Map reference
}

UI Integration

Campaign Selection

The CampaignMenu displays available campaigns and their missions:

  1. User selects a campaign
  2. Mission list appears with:
  3. Order number
  4. Mission name
  5. Intro text preview
  6. User selects specific mission
  7. Mission loads with proper configuration

Victory Conditions

Mission-specific victory conditions override map defaults:

Best Practices

Mission Design

  1. Keep Maps Reusable: Don't hardcode gameplay in map files
  2. Clear Objectives: Provide explicit victory/defeat conditions
  3. Balanced Difficulty: Test with various player skill levels
  4. Progressive Complexity: Early missions should be simpler

Campaign Structure

  1. Logical Progression: Order missions by difficulty
  2. Narrative Flow: Use intro/outro text for story
  3. Varied Gameplay: Mix victory condition types
  4. Testing: Validate entire campaign sequences

Asset Organization

assets/missions/
  β”œβ”€β”€ 01_tutorial/
  β”‚   β”œβ”€β”€ basic_combat.json
  β”‚   └── defend_base.json
  β”œβ”€β”€ 02_main_campaign/
  β”‚   β”œβ”€β”€ forest_battle.json
  β”‚   └── siege_warfare.json
  └── 03_advanced/
      └── survival_challenge.json

Troubleshooting

Common Validation Errors

"Mission not found" - Check mission_id matches filename - Ensure mission file is in assets/missions/

"Order index not contiguous" - Campaign missions must have sequential indices - No gaps allowed (0, 1, 2, ... or 1, 2, 3, ...)

"Referenced map not found" - Verify map_path is correct - Check map file exists in assets/maps/

Debugging

Enable verbose logging:

QT_LOGGING_RULES="*.debug=true" ./standard_of_iron

Check mission loading:

qDebug() << "Loading mission:" << mission.id;
qDebug() << "Victory conditions:" << mission.victory_conditions.size();

Future Enhancements

Planned features:

API Reference

See also: - game/map/mission_definition.h - Mission data structures - game/map/campaign_definition.h - Campaign data structures - game/map/mission_loader.h - Mission loading API - game/map/campaign_loader.h - Campaign loading API - tools/content_validator/ - Validation implementation