Visible AI: A New Game Design Paradigm
In most games, the AI is a black box. Enemies do things, and you react. There's no way to read their intentions, predict their moves, or outthink them — only out-reflex them.
What if the AI's behavior was visible? What if you could read an enemy's state machine and use it against them?
That's what we built in Trait Wars.
The Black Box Problem
In traditional game AI, enemy behavior looks like this from the player's perspective:
Enemy does... something.
You take damage.
Why? Who knows.
Under the hood, it's a mess of weighted random decisions:
# Traditional game AI
def decide_action(enemy, player):
if enemy.hp < 30:
if random() < 0.6:
return "flee"
else:
return "desperate_attack"
elif distance(enemy, player) < 3:
if random() < 0.7:
return "melee_attack"
else:
return "block"
else:
return "approach"
Random weights, nested conditionals, invisible state. The player can't meaningfully interact with this system. They can only react faster.
This is why most game AI feels "unfair" or "stupid" — never intelligent.
The Visible AI Principle
Trait Wars inverts this. Every unit in the game has Traits — and traits are state machines. The player can see:
- What state the enemy is in (Idle, Aggressive, Defending, Enraged)
- What events trigger transitions (ATTACK, TAKE_DAMAGE, LOW_HP)
- What the enemy will do when a transition fires (effects)
This transforms combat from a reflex game into a strategy game about reading and manipulating state machines.
How It Works
Trait: The Core of Every Unit
Each unit in Trait Wars equips traits. A trait is a visible state machine:
{
"name": "BerserkerTrait",
"linkedEntity": "Unit",
"stateMachine": {
"states": [
{ "name": "Calm", "isInitial": true },
{ "name": "Aggressive" },
{ "name": "Enraged" }
],
"events": [
{ "key": "TAKE_DAMAGE", "name": "Take Damage" },
{ "key": "KILL_ENEMY", "name": "Kill Enemy" },
{ "key": "REST", "name": "Rest" }
],
"transitions": [
{
"from": "Calm",
"to": "Aggressive",
"event": "TAKE_DAMAGE",
"guard": ["<", "@entity.hp", ["*", "@entity.maxHp", 0.5]],
"effects": [
["set", "@entity.attackMultiplier", 1.5],
["set", "@entity.defenseMultiplier", 0.8]
]
},
{
"from": "Aggressive",
"to": "Enraged",
"event": "TAKE_DAMAGE",
"guard": ["<", "@entity.hp", ["*", "@entity.maxHp", 0.25]],
"effects": [
["set", "@entity.attackMultiplier", 2.5],
["set", "@entity.defenseMultiplier", 0.4]
]
},
{
"from": "Enraged",
"to": "Calm",
"event": "KILL_ENEMY",
"effects": [
["set", "@entity.attackMultiplier", 1.0],
["set", "@entity.defenseMultiplier", 1.0],
["set", "@entity.hp", ["*", "@entity.maxHp", 0.3]]
]
}
]
}
}
The player can see this trait on the enemy unit. They know:
- Calm → Normal stats. Safe to ignore for now.
- Aggressive → 1.5x attack, 0.8x defense. Dangerous but fragile.
- Enraged → 2.5x attack, 0.4x defense. A glass cannon. Hit them now or die.
- Kill trigger → If the Berserker kills someone while Enraged, it resets to Calm and heals. Don't let it get the kill.
Player Strategy Emerges
Because the AI is visible, combat becomes about manipulating enemy state: