SDK Documentation
The complete API reference for the Warweaver deterministic cognition
runtime. Everything you need to wire the Brain to the Body.
Just want the code? Jump to
Quick Start →
Core Philosophy
Warweaver is opinionated. We believe that to simulate tens of thousands of autonomous agents, you cannot rely on MonoBehaviours, coroutines, or object-oriented hierarchies. We use a data-oriented, allocation-free architecture so you never worry about GC spikes during a battle.
Warweaver is a deterministic agent simulation engine. Unity handles rendering, physics, and game logic. Warweaver handles cognition — what your agents think, perceive, and decide. They communicate via telemetry. No coupling. No shared state.
Three design principles drive every API decision:
- Determinism above all. Same seed + same commands = same result. Every time. This gives you forensic-grade debugging: replay any tick, trace any decision.
- Zero allocation in the hot loop. The simulation runs on its own thread at 30 TPS. Rendering runs at 144fps. They don't block each other. No GC. No stalls.
- Clean separation. Warweaver never touches Unity APIs. Unity never touches simulation state. Signals go in, telemetry comes out. That's the contract.
Quick Start
1. Add WarweaverManager to Your Scene
// Attach WarweaverManager to a GameObject in your scene
// Configure in Inspector:
// Lockfile Path: "lockfile.json" (relative to StreamingAssets)
// Target Ticks Per Second: 5
2. Access the API
using Warweaver.UnityAdapter;
public class MyGameController : MonoBehaviour
{
private WarweaverManager _manager;
private WarweaverAdapter _adapter;
void Start()
{
_manager = FindObjectOfType<WarweaverManager>();
_adapter = _manager.Adapter;
// Define your schema (before simulation starts)
var hpHandle = _adapter.CreateAttribute("HP", 0f, 100f, 100f);
// Start simulation
_manager.StartSimulation();
}
}
The Four-Step Pattern
This is how you wire the Brain to the Body.
01. Define
Create your world schema: attributes, operators, and archetypes. This is configuration data, separate from runtime execution. You do this once, before Tick 0. The schema locks. No mid-simulation changes. This is "The Bake."
02. Inject
Your Unity scripts inject signals into the kernel (events, player actions, world state changes). The kernel processes these deterministically in the next tick. No immediate side effects. No Unity callbacks from the kernel. Clean separation.
03. Scale
Step the simulation at fixed timestep (30 TPS). Read telemetry snapshots (alloc-free structs) to drive Unity rendering and UI updates. The simulation runs on its own thread. Rendering runs at 144fps. They don't block each other.
04. Inspect
Use reasoning traces to debug agent behavior. Understand why agents made specific decisions at specific ticks. Replay the exact tick with the same seed. Same result. No guessing.
Simulation Control
WarweaverManager
The WarweaverManager MonoBehaviour is your entry point.
// Start the simulation loop (runs in background)
_manager.StartSimulation();
// Stop the simulation
_manager.StopSimulation();
// Step one tick (for frame-by-frame control)
_manager.StepSimulation();
// Reset to initial state
_manager.ResetSimulation();
_manager.ResetSimulation(newSeed: 12345); // With new seed
Properties
Adapter— The WarweaverAdapter for Unity integrationHost— Direct access to ISimulationHost (advanced)CurrentTick— Current simulation tickIsInitialized— Whether the system is ready
Lifecycle Events
OnInitialized— Fired when host is readyOnSimulationStarted— Fired when simulation loop beginsOnTick— Fired after each simulation step
Schema Definition
All schema definitions must happen before the simulation starts. This is "The Bake."
Attributes
Attributes are agent properties (HP, Strength, Hunger, etc.).
// Create attributes during setup
var hpHandle = _adapter.CreateAttribute("HP", min: 0f, max: 100f, defaultValue: 100f);
var strengthHandle = _adapter.CreateAttribute("Strength", 1f, 100f, 10f);
Archetypes
Archetypes define agent types (Warrior, Priest, Goblin, etc.).
int warriorId = _adapter.CreateArchetype("Warrior", new ArchetypeConfig
{
RequiredAttributes = new[] { hpHandle, strengthHandle },
AllowedOperators = new[] { attackOpId, chargeOpId }
});
int priestId = _adapter.CreateArchetype("Priest", new ArchetypeConfig
{
RequiredAttributes = new[] { hpHandle, mpHandle },
AllowedOperators = new[] { healOpId, blessOpId }
});
Signals
Signals are events that agents can perceive.
int noiseSignalId = _adapter.CreateSignal("Noise");
int dangerSignalId = _adapter.CreateSignal("Danger");
Operators
Operators are actions agents can perform.
int attackOpId = _adapter.CreateOperator("Action_Attack");
int healOpId = _adapter.CreateOperator("Action_Heal");
int fleeOpId = _adapter.CreateOperator("Action_Flee");
Agent Management
Spawning Agents
Deterministic Pattern (Recommended)
// 1. Enqueue the spawn command
long commandId = _adapter.EnqueueSpawnAgent(
archetypeId: warriorId,
position: new Vector2(10f, 20f),
factionId: 1
);
// 2. Step the simulation to execute
_manager.Host.Step();
// 3. Get the spawned agent ID
ushort agentId = _adapter.WaitForSpawnAck(commandId);
Convenience Method
// Auto-steps the simulation (use with caution in deterministic scenarios)
ushort agentId = _adapter.SpawnAgent(warriorId, new Vector2(10f, 20f), factionId: 1);
Ergonomic Agent Access
// Get a single agent
var agent = _adapter.GetAgent(agentId);
var position = agent.GetPosition();
var hp = agent.GetAttribute(hpHandle);
// Get all agents
var allAgents = _adapter.GetAllAgents();
foreach (var a in allAgents)
{
// Process each agent
}
State Management
Setting Attributes
// Set HP for an agent
_adapter.SetAttribute(agentId, hpHandle, 75f);
Getting Attributes
// Get current HP (queries CurrentTick)
float hp = _adapter.GetAttribute(agentId, hpHandle);
// Query a specific historical tick
float hpAtTick5 = _adapter.GetAttribute(agentId, hpHandle, tick: 5);
Dirty Tracking
Only sync agents that changed:
// Get agents whose attributes changed this tick
ushort[] dirtyAgents = _adapter.GetDirtyAgentIds();
foreach (var id in dirtyAgents)
{
// Update only these GameObjects
float hp = _adapter.GetAttribute(id, hpHandle);
UpdateHealthBar(id, hp);
}
Telemetry
Warweaver provides two telemetry patterns:
Double-Buffer Pattern (Zero Allocation)
For high-performance scenarios with 1000+ agents:
void Update()
{
// Swap buffers at frame start
_adapter.SwapTelemetryBuffers();
// Read from front buffer (stable until next swap)
var snapshot = _adapter.GetTelemetrySnapshot();
for (int i = 0; i < snapshot.AgentCount; i++)
{
var agent = snapshot.Agents[i];
UpdateAgentPosition(agent.AgentId, agent.X, agent.Y);
}
}
Zero-Copy NativeArray Views
For Unity Jobs and Burst:
// Get direct NativeArray views (no allocation, no copy)
NativeArray<float> positionsX = _adapter.GetPositionXView();
NativeArray<float> positionsY = _adapter.GetPositionYView();
NativeArray<int> factionIds = _adapter.GetFactionIdView();
NativeArray<int> currentActions = _adapter.GetCurrentActionView();
// Use in Burst job
[BurstCompile]
struct UpdatePositionsJob : IJobParallelFor
{
[ReadOnly] public NativeArray<float> PositionsX;
[ReadOnly] public NativeArray<float> PositionsY;
public NativeArray<float3> Transforms;
public void Execute(int i)
{
Transforms[i] = new float3(PositionsX[i], 0f, PositionsY[i]);
}
}
Check View Validity
var version = _adapter.GetBufferVersion();
// ... use views ...
// Check if buffer was resized (views invalidated)
if (!_adapter.IsViewValid(version))
{
// Re-acquire views
}
Cognitive LOD
Process only visible agents at full fidelity:
var activeSet = _adapter.GetActiveSet();
// Only update GameObjects for active agents
foreach (var agentId in activeSet.ActiveAgentIds)
{
// Full GameObject update (position, animation, effects)
}
// Background agents get minimal updates
foreach (var agentId in activeSet.BackgroundAgentIds)
{
// Position only, no animation
}
Agent Commands
Read what actions agents are performing:
var commands = new AgentCommandOutput[100];
int count = _adapter.GetAgentCommands(out commands);
for (int i = 0; i < count; i++)
{
var cmd = commands[i];
if (cmd.HasActiveOperator)
{
TriggerAnimation(cmd.AgentId, cmd.ActiveOperatorId);
if (cmd.HasTargetEntity)
{
PlayAttackEffect(cmd.AgentId, cmd.TargetAgentId);
}
}
}
Perception
Injecting Perception (Unity → Simulation)
Tell the simulation what agents can see:
// From Unity's line-of-sight system
int[] visibleEntities = GetVisibleEntitiesFromRaycasts(agentId);
// Inject into simulation
_adapter.UpdatePerception(agentId, visibleEntities);
Querying Perception (Simulation → Unity)
Read what agents perceive:
// Get all observations for an agent
var observations = _adapter.GetObservations(agentId);
foreach (var obs in observations)
{
Debug.Log($"Agent {agentId} observed entity {obs.SubjectId}");
}
// Or just get visible entities
int[] visible = _adapter.GetVisibleEntities(agentId);
Injecting Signals
Create events that agents can perceive:
long commandId = _adapter.InjectSignal(
signalId: noiseSignalId,
position: new System.Numerics.Vector3(50f, 0f, 50f),
radius: 30f,
duration: 60 // ticks
);
Intent System
The simulation emits Intents when agents want to act. Unity executes them and reports results.
Polling Intents
void Update()
{
Intent[] intents = _manager.Host.PollIntents();
foreach (var intent in intents)
{
switch (intent.Kind)
{
case IntentKind.MoveTo:
var target = intent.Payload.MoveTo;
ExecuteMovement(intent.AgentId, target.X, target.Y, intent.IntentId);
break;
case IntentKind.UseAction:
var action = intent.Payload.UseAction;
ExecuteAction(intent.AgentId, action, intent.IntentId);
break;
}
}
}
Reporting Results
// Success
_manager.Host.ReportIntentResult(intentId, IntentResultCode.Success);
// Failed
_manager.Host.ReportIntentResult(intentId, IntentResultCode.Failed,
reason: new IntentResultReason { Message = "Path blocked" });
// Blocked (with cooldown)
_manager.Host.ReportIntentResult(intentId, IntentResultCode.Blocked,
reason: new IntentResultReason { CooldownTicks = 30 });
Affordances
Push runtime constraints to filter operator selection:
var affordanceSet = new AffordanceSet
{
AgentId = agentId,
Tick = _manager.CurrentTick,
Affordances = new[]
{
new Affordance("Action_Fireball", isAvailable: false, blockReason: "On cooldown"),
new Affordance("Action_Heal", isAvailable: true)
}
};
_manager.Host.PushAffordances(agentId, affordanceSet);
Spatial Queries
// Get agents within radius
ushort[] nearby = _manager.Host.GetAgentsInRadius(50f, 50f, radius: 20f);
// Get nearest agent
ushort? nearest = _manager.Host.GetNearestAgent(50f, 50f);
// Check if agent exists
bool isValid = _manager.Host.IsValidAgentId(agentId);
// Check if queries are allowed
if (_manager.Host.CanQuery())
{
// Safe to query
}
API Reference
WarweaverAdapter 25 methods
| Method | Description |
|---|---|
| CreateAttribute(name, min, max, default) | Define an attribute in the schema |
| CreateArchetype(name, config) | Define an archetype in the schema |
| CreateSignal(name) | Define a signal in the schema |
| CreateOperator(name) | Define an operator in the schema |
| EnqueueSpawnAgent(archetype, position, faction?) | Queue a spawn command |
| WaitForSpawnAck(commandId) | Wait for spawn acknowledgment |
| SpawnAgent(archetype, position, faction?) | Spawn with auto-step |
| SetAttribute(agentId, handle, value) | Set an attribute value |
| GetAttribute(agentId, handle, tick?) | Get an attribute value |
| GetDirtyAgentIds() | Get agents with changed attributes |
| GetTelemetrySnapshot() | Get current telemetry front buffer |
| SwapTelemetryBuffers() | Swap telemetry double buffer |
| GetPositionXView() | Zero-copy NativeArray of X positions |
| GetPositionYView() | Zero-copy NativeArray of Y positions |
| GetFactionIdView() | Zero-copy NativeArray of faction IDs |
| GetCurrentActionView() | Zero-copy NativeArray of current actions |
| GetCurrentActionConfidenceView() | Zero-copy NativeArray of action confidence |
| IsViewValid(version) | Check if buffer view is still valid |
| GetActiveSet() | Get ActiveSet for cognitive LOD |
| GetAgent(agentId) | Get ergonomic AgentHandle |
| GetAllAgents() | Get all AgentHandles |
| GetAgentCommands(out buffer) | Get agent command output |
| UpdatePerception(agentId, visibleIds) | Inject perception data |
| GetObservations(agentId) | Get agent observations |
| GetVisibleEntities(agentId) | Get visible entity IDs |
| InjectSignal(signalId, position, radius, duration) | Inject a signal |
ISimulationHost 15 methods
| Method | Description |
|---|---|
| Step() | Advance one tick (synchronous) |
| StartAsync(CancellationToken) | Start background simulation loop |
| StopAsync() | Stop simulation loop |
| Reset(seed?) | Reset to initial state |
| EnqueueCommand(command) | Queue a command |
| PollIntents(tick?) | Get intents emitted by agents |
| ReportIntentResult(intentId, code, reason?) | Report intent execution result |
| PushAffordances(agentId, set) | Push runtime constraints |
| GetAgentsInRadius(x, y, radius, tick?) | Spatial query |
| GetNearestAgent(x, y, tick?) | Nearest agent query |
| IsValidAgentId(agentId) | Check agent validity |
| CanQuery(tick?) | Check if queries are allowed |
| CurrentTick | Current simulation tick |
| Schema | Access WorldSchema |
| CommandAckStream | Stream of command acknowledgments |
WarweaverManager 8 methods
| Method | Description |
|---|---|
| Initialize() | Initialize the system |
| StartSimulation() | Start simulation loop |
| StopSimulation() | Stop simulation loop |
| StepSimulation() | Step one tick |
| ResetSimulation(seed?) | Reset simulation |
| Adapter | Get WarweaverAdapter |
| Host | Get ISimulationHost |
| CurrentTick | Current tick |
Best Practices
- Define schema in Awake() — All attributes, archetypes, signals, operators before simulation starts
- Use deterministic spawning —
EnqueueSpawnAgent+Step+WaitForSpawnAckfor replay-safe code - Swap buffers at frame start —
SwapTelemetryBuffers()thenGetTelemetrySnapshot() - Use zero-copy views for Jobs —
GetPositionXView()for Burst-compiled rendering - Track dirty agents —
GetDirtyAgentIds()for sparse sync instead of full updates - Report intent results — Always report Success/Failed/Blocked to close the loop
- Check query validity —
CanQuery()before spatial queries during tick processing
Common Patterns
Merchant With Dynamic Pricing
A merchant NPC that adjusts prices based on player reputation. Uses schema attributes for reputation and favor, signals for trade events, and telemetry to drive UI.
// Schema setup
var reputationHandle = _adapter.CreateAttribute("Reputation", -100f, 100f, 0f);
var favorHandle = _adapter.CreateAttribute("Favor", 0f, 100f, 50f);
int tradeSignalId = _adapter.CreateSignal("TradeCompleted");
// Later: inject a trade event when player buys from the merchant
_adapter.InjectSignal(
signalId: tradeSignalId,
position: merchantPosition,
radius: 0f, // only this merchant perceives it
duration: 1 // single tick
);
// Read the merchant's updated favor via telemetry
float favor = _adapter.GetAttribute(merchantId, favorHandle);
float priceMultiplier = Mathf.Lerp(1.5f, 0.7f, favor / 100f);
UpdateShopUI(priceMultiplier);
Village Evacuation via Signal Propagation
A scout spots danger and triggers an emergent village-wide evacuation — no scripted sequence, just signal propagation and autonomous decision-making.
// When the scout's perception detects an enemy
int dangerSignalId = _adapter.CreateSignal("Danger");
// Inject a danger signal with large radius
_adapter.InjectSignal(
signalId: dangerSignalId,
position: enemyPosition,
radius: 100f, // all villagers within 100 units perceive it
duration: 120 // persists for 4 seconds at 30 TPS
);
// No script tells villagers to flee.
// Each agent's BDI/GOAP planner evaluates the danger signal
// against their own goals, personality, and current state.
// Warriors might charge. Civilians might flee. Priests might heal.
// The emergence IS the game.
Forensic Debugging
An NPC made a surprising decision. Replay the exact moment to understand why.
// You noticed Agent #47 attacked an ally at tick 1,234
// Step 1: Reset with the same seed
_manager.ResetSimulation(originalSeed);
// Step 2: Advance to the tick before the decision
for (int t = 0; t < 1233; t++)
_manager.StepSimulation();
// Step 3: Step one tick and inspect the reasoning trace
_manager.StepSimulation();
// Now examine what Agent #47 saw and decided
var observations = _adapter.GetObservations(47);
var commands = _adapter.GetAgentCommands(out var cmdBuffer);
// The trace reveals: Agent #47 perceived the ally as hostile
// because a "Betrayal" signal was injected at tick 1,200.
// Determinism means this will reproduce every single time.
Ready to Build?
Ship the game your players will never stop talking about.
Docs unclear? Something missing? Ping me in the Discord. I’ll fix it the same day.