Last modified: June 06, 2026
This article is written in: 🇺🇸
This document describes the current enemy AI in Standard of Iron: what it already does well, how it is configured, where mission JSON plugs into it, and which expansions still remain before it reaches a fuller professional RTS standard.
The AI is no longer a passive "units occasionally wander" layer. It now has a cheap centralized planner that can:
It is still intentionally lightweight: one AI brain per player, throttled updates, immutable snapshots, small force-role heuristics, and behavior modules instead of expensive per-unit thinking.
The system optimizes for four things:
The AI runs in a snapshot -> reason -> execute -> apply pipeline.
world state
|
v
AISnapshotBuilder
|
v
AISnapshot (immutable, thread-safe)
|
v
AIReasoner
- updates persistent context
- refreshes force roles
- advances state machine
|
v
AIExecutor
- runs eligible behaviors by priority
- emits AICommand list
|
v
AICommandFilter / AICommandApplier
|
v
game world
The expensive part is the thinking, so it is throttled and handed to a worker thread. The snapshot is immutable specifically so AI code can reason off-thread without touching live world state.
Most AI code lives in game/systems/ai_system/.
| File | Responsibility |
ai_types.h |
Snapshot, context, strategy config, commands |
ai_snapshot_builder.cpp |
Reads visible world state into AISnapshot |
ai_reasoner.cpp |
Updates persistent AI context and state |
ai_executor.cpp |
Runs behaviors and collects commands |
ai_worker.cpp |
Background worker wrapper |
ai_command_filter.cpp |
Prevents duplicate/spammy commands |
ai_command_applier.cpp |
Applies AI commands back to the game |
ai_strategy.cpp |
Strategy presets, personality shaping, difficulty tuning |
ai_utils.h |
Assignment cleanup and force-role helper functions |
behaviors/*.cpp |
Tactical and macro behavior implementations |
game/systems/ai_system.cpp |
Owns AI instances and update cadence |
The AI uses two data models:
AISnapshot: what is true right nowAIContext: what the AI remembersAISnapshot is intentionally compact:
friendly_unitsvisible_enemiesstrategic_objectivesgame_timeThe important change is strategic_objectives: the AI keeps enemy structures and commanders as long-range objectives even when they are outside tactical vision. That prevents the classic RTS failure mode where the army stops doing anything just because no enemy is currently visible.
AIContext is where most of the AI’s current strength lives. In addition to basic unit counts and state, it tracks:
last_local_threat_time)has_expansion_siteexpansion_site_x/zoutpost_barracks_countoutpost_home_countexpansion_construction_pendinglast_expansion_order_timeThis is still heuristic AI, not a heavyweight planner, but the persistent context makes it feel much more intentional.
The AI operates in these strategic states:
IdleGatheringAttackingDefendingRetreatingExpandingThe transitions are driven by cheap battlefield signals:
DefendingRetreatingAttackingExpandingGatheringThe important modern behavior is that Defending is no longer sticky forever. It decays from local threat memory instead of global enemy visibility, so the AI can leave defense mode once the base area has actually calmed down.
Behaviors are modular and ordered by priority.
| Behavior | Priority | Concurrent? | Current job |
RetreatBehavior |
Critical | No | Pull damaged armies back to safety |
DefendBehavior |
Critical | No | React to local threats, prefer reserve first |
ProductionBehavior |
High | Yes | Keep barracks producing from style-aware targets |
BuilderBehavior |
High | Yes | Build homes, barracks, towers, catapults, and outposts |
CommanderBehavior |
High | Yes | Move commanders and trigger rally ability |
ExpandBehavior |
High | No | Capture neutral barracks or escort the main force to an outpost site |
AttackBehavior |
Normal | No | Main-army pushes, target chasing, blind marches to strategic objectives |
HarassBehavior |
Low | Yes | Raider detachment against isolated or strategic targets |
GatherBehavior |
Low | No | Assemble the main army around the rally area |
Three concurrency rules matter:
This is the current force model.
The main army is the attack-capable pool after excluding:
GatherBehavior organizes this force near the rally point, and AttackBehavior uses it for proactive attacks and objective marches.
The reserve is a stable home-defense group stored in reserve_unit_ids.
Current rules:
DefendBehavior prefers reserve firstGatherBehavior keeps reserve near baseAttackBehavior never sends reserve units forwardThis is the first real "do not commit everything" rule in the AI.
The harass force is a separate detachment stored in harass_unit_ids.
Current rules:
This gives the AI a second offensive layer without making the main planner much heavier.
Commanders are handled separately:
The AI now uses shared macro targets instead of scattered hardcoded thresholds.
AIStrategyConfig feeds these targets into context:
BuilderBehavior then builds toward the largest deficit while preserving important early priorities like homes and the first barracks.
ProductionBehavior also reads from the same config, so unit production and structure growth are at least pulling in the same strategic direction.
The AI now has a first outpost planner.
So this is a real step beyond passive single-base AI, but it is not yet a full multi-base RTS economy.
The system now deliberately separates what the AI wants from how efficiently it executes.
The strategy preset is the coarse style template:
balancedaggressivedefensiveexpansionisteconomicharasser / harassmentrusher / rushThese presets set the default shape of the AI:
Mission JSON can then nudge a preset using three normalized floats:
aggressiondefenseharassmentThese values tune things like:
Difficulty now affects execution efficiency, not strategic identity.
Supported values:
easyhardvery_hardmedium currently falls back to normalnormalDifficulty currently changes:
That means a defensive AI on hard is still defensive; it just reacts and scales more efficiently.
Mission files are the current authoring surface for AI setup. The loader reads strategy, personality, difficulty, team_id, starting spawns, and optional mission waves from ai_setups.
{
"id": "roman_legion_alpha",
"nation": "roman_republic",
"faction": "roman",
"color": "red",
"team_id": 1,
"difficulty": "hard",
"strategy": "balanced",
"personality": {
"aggression": 0.62,
"defense": 0.55,
"harassment": 0.30
},
"starting_buildings": [
{
"type": "barracks",
"position": { "x": 132, "z": 84 },
"max_population": 180
}
],
"starting_units": [
{
"type": "spearman",
"count": 8,
"position": { "x": 128, "z": 86 }
},
{
"type": "builder",
"count": 2,
"position": { "x": 134, "z": 82 }
}
],
"commander_troop": "roman_field_commander"
}
Resulting feel:
easy because the execution cadence is faster{
"id": "numidian_raiders",
"nation": "carthage",
"faction": "carthaginian",
"color": "yellow",
"difficulty": "medium",
"strategy": "harasser",
"personality": {
"aggression": 0.74,
"defense": 0.28,
"harassment": 0.84
},
"starting_units": [
{
"type": "horse_swordsman",
"count": 5,
"position": { "x": 18, "z": 80 }
},
{
"type": "builder",
"count": 1,
"position": { "x": 16, "z": 82 }
}
],
"starting_buildings": [
{
"type": "barracks",
"position": { "x": 14, "z": 80 },
"max_population": 120
}
]
}
Resulting feel:
strategy is optional; omitted means balancedpersonality fields default to 0.5difficulty can be omitted; the AI falls back to normal execution tuningteam_id is optional; omitted AIs are auto-assigned separate enemy teamswaves add scripted reinforcements on top of the normal AI economy/behavior layerThe main regression coverage lives in tests/systems/ai_system_test.cpp.
Current AI test coverage includes:
For repo validation, the reliable test binary is:
./build/bin/standard_of_iron_tests --gtest_color=no --gtest_brief=1
Relative to the original passive AI, the current system is much better at:
The AI is improved, but it is not yet "finished RTS AI." The most important remaining gaps are:
better outpost abandonment / retarget logic
Richer force planner
regroup / reform logic after failed pushes
Data-driven profiles
let designers tune AI personalities without recompiling
Stronger strategic economy awareness
broader structure placement logic
Team and campaign coordination
If you want the next biggest gains per engineering effort, the recommended order is:
That sequence builds on the current architecture instead of fighting it.