MIRAI Deep Dive: Building Adaptive Enemy AI
Why Build a Framework
Inhyeong started with simple enemy AI — hardcoded callbacks for attacks, a flat behavior tree, no memory. It worked for one enemy type. The moment we needed a second enemy that fought differently, the cracks showed. Every new behavior meant duplicating tree construction code, every parameter change meant touching multiple files. The system was rigid where it needed to be flexible.
MIRAI came out of that pain. The name stands for Modular Intelligent Response & Adaptive Interaction — the goal was an AI framework where creating a new enemy type meant selecting and configuring existing building blocks, not writing behavior trees from scratch.
The Builder Pattern Decision
The biggest architectural choice was adopting the builder pattern. Instead of each enemy class constructing its own behavior tree node by node, enemies call ConfigureBuilders() and select from three builder interfaces:
- ICombatBehaviorBuilder — handles target detection, attack sequences, special abilities
- IMovementBehaviorBuilder — patrol, wander, return-to-spawn, stuck recovery
- IGoalBehaviorBuilder — objective pursuit, collection behaviors
A fast melee enemy and a slow tank both use MeleeCombatBuilder with different parameters. Same builder, different configuration, completely different feel in combat. When we later needed ranged enemies, we wrote RangedCombatBuilder once and every ranged enemy got it for free.
The base class AdvancedBehaviorTree orchestrates builder output and integrates the three subsystems — memory, personality, and goals. Four abstract methods define the enemy’s stats (GetAttackRange(), GetAttackCooldown(), GetAggroRadius(), GetPlayerLayer()), and everything else is configuration.
Services Over Polling
One of the more impactful refactors was replacing polling nodes with service nodes. Previously, target scanning was a FindTargetNode that ran in the tree evaluation — blocking other logic while it searched. Now TargetScanService runs in the background on a 0.6s interval, updates the blackboard, and every node that needs target info just reads from the blackboard.
HealthMonitorService did the same for health checks. Instead of scattered health queries across combat, environment, and goal nodes, one service tracks health state transitions and writes to the blackboard. The tree went from “check health everywhere” to “read health state once.”
Soul Recollections: Teaching Enemies to Remember
The memory system was the first major subsystem. The core question: what if enemies remembered how past fights went?
SoulRecollections stores three memory types per player — combat memory (damage dealt/received, threat level, ability observations), social memory (friendliness on a -1 to +1 scale), and ability memory (per-ability effectiveness tracking). Memories have importance levels from Trivial to Critical, with decay rates tied to importance — minor details fade in minutes, getting killed by a player persists for the session.
The interesting behaviors came from memory sharing. When one enemy observes a dangerous player, it warns nearby allies via radius-based sharing. A pack of enemies that’s fought you before approaches differently than one that hasn’t — they already know your threat level and which of your abilities hurt most.
Network sync uses selective batching: critical updates (player just killed an ally) sync immediately, minor observations get deferred and grouped. This kept bandwidth reasonable with many AI agents.
Soul Personality: Making Every Enemy Feel Different
Memory made enemies adaptive. Personality made them individual.
Seven personality attributes (each 0–100) drive behavior through eight modifiers. The attributes aren’t generic — they’re themed around the game’s lore. Resonance is connection to purpose, Tethering is connection to place, Clarity is self-awareness, Essence Yearning drives collection behavior, Harmony affects movement smoothness, Memory Weave determines how fast they learn, and Emotional Echo is their current emotional state.
The modifier system is where personality becomes visible in gameplay. A high-Aggression enemy has reduced attack cooldowns, extended pursuit range, and minimal retreat behavior. A high-Caution enemy retreats earlier, uses defensive positioning, and takes longer between attacks. Same MeleeCombatBuilder, dramatically different fight.
What made the system interesting was personality evolution. Taking heavy damage increases Caution and can trigger Fear. Defeating players increases Clarity and Confidence. Soul/Essence damage types cause dramatic personality shifts — potentially inverting attributes entirely. An enemy that starts aggressive can become cautious after getting beaten enough times.
Enemy tiers use different amounts of the personality system. Basic Remnants only have 3–4 active attributes with slow evolution. Elite Manifested Entities have all seven with fast evolution. Bosses (Ancients/Guardians) can undergo personality phase transitions mid-fight.
Ability Intelligence: Situational Learning
The ability system was the most recent addition. The problem it solved: enemies with multiple abilities would use them randomly or follow a fixed priority. A wolf with both a basic attack and a spin attack shouldn’t always spin — it should learn when spinning works.
The solution was a three-layer memory architecture. Species knowledge is shared (all wolves collectively learn spin attack effectiveness), individual experience is per-enemy, and player-specific memory adapts to your playstyle. Abilities are understood through SpellTags — the AI knows an ability is AoE, or a Shield, or Crowd Control, and scores accordingly.
Situational learning was the breakthrough feature. Instead of “Fireball works 60% of the time,” the system learns “Fireball works 95% when there are many enemies nearby, but only 50% against a solo target.” Situations are bucketed into discrete categories (HP level, enemy count, ally presence) so patterns emerge quickly — typically within 3–5 uses.
Personality feeds into ability selection too. An aggressive enemy scores AoE abilities higher when surrounded. A cautious enemy at low HP scores movement abilities higher. Same available abilities, different usage patterns based on who the enemy is.
Enemies can also learn new abilities at runtime. CrystallizedEssence items in the world contain abilities that enemies can collect through the goal system. Boss evolution systems can programmatically teach phase-specific abilities when health thresholds are crossed.
The Debugger
Building all of this without being able to see what the AI was thinking would have been miserable. The visual debugger was developed alongside the framework — a custom Unity Editor window that renders the full behavior tree as a pannable graph with nine specialized tabs.
The Activity Log tab was the most useful during development. It translates raw node execution into plain language — “Enemy acquired target: Player1” instead of “TargetScanService set CurrentTarget.” The Blackboard tab shows all key-value state in real-time, categorized by function. The Breakpoint system lets you pause execution on specific conditions.
Zero-overhead design was important with many AI agents. Only the actively debugged tree has instrumentation cost — every other tree runs a single null check per evaluation and nothing else.
Where It Is Now
MIRAI powers all enemy AI in Inhyeong. The builder pattern has held up well — new enemy types take an afternoon instead of a week. The memory and personality systems create emergent behaviors that surprise even during development. The framework is currently in the polish phase, focusing on edge cases in network synchronization and tuning personality evolution rates.
The dedicated MIRAI page has the full technical breakdown of every system if you want the specifics.