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:

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

Lifecycle Events

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

  1. Define schema in Awake() — All attributes, archetypes, signals, operators before simulation starts
  2. Use deterministic spawningEnqueueSpawnAgent + Step + WaitForSpawnAck for replay-safe code
  3. Swap buffers at frame startSwapTelemetryBuffers() then GetTelemetrySnapshot()
  4. Use zero-copy views for JobsGetPositionXView() for Burst-compiled rendering
  5. Track dirty agentsGetDirtyAgentIds() for sparse sync instead of full updates
  6. Report intent results — Always report Success/Failed/Blocked to close the loop
  7. Check query validityCanQuery() 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.