Porting Mistreevous to C#: A High-Performance Behavior Tree Library for Modern .NET

Published: (December 13, 2025 at 07:02 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

When I started building dedicated servers for multiplayer games, I ran into a very real problem: server‑side entities needed rich, structured, and consistent AI behavior. In single‑player games the client usually handles this, but in modern multiplayer titles with authoritative servers the AI must run server‑side — and that completely changes the performance and architectural demands.

Behavior Trees were the obvious choice. I fell in love with Mistreevous, a beautifully designed TypeScript library by nikkorn, thanks to its clean, modular, and highly expressive DSL. The challenge? Bring that same elegance to the .NET world — but optimized for the harsh reality of server tick loops.

Why Behavior Trees?

Behavior Trees shine in games, robotics, simulations, and any agent‑based system because they offer:

  • Hierarchical composition
  • Excellent readability and intent clarity
  • Strong modularity
  • Easy debugging
  • Predictable execution order

For multiplayer servers specifically, they deliver:

  • Extremely low cost per tick
  • Deterministic outcomes
  • Clean separation of logic and state
  • Horizontal scaling across hundreds of entities

Mistreevous already nailed the design in TypeScript. The C# version had to be just as expressive — but truly server‑ready.

Why Mistreevous Specifically?

The original Mistreevous stands out with:

  • A fluid, readable DSL
  • Well‑defined node types
  • Simple parsing
  • Full JSON compatibility
  • An intuitive API

It was built for a JavaScript runtime. In .NET (especially .NET 9 and .NET Standard 2.1) we have far more control over memory and performance. The goal: keep the spirit and compatibility, but eliminate allocations and make it scream on the server.

Not Just a Port — A Performance‑Focused Rewrite

While staying 100 % semantically compatible with the original, almost every internal detail was re‑engineered.

Zero‑Allocation: The Core Principle

The primary objective was simple: calling Step() (one AI tick) must generate zero garbage. On a server with dozens or hundreds of entities ticking 30–60 times per second, even tiny repeated allocations become a GC nightmare.

Key techniques applied:

  • Eliminated LINQ in hot paths – no enumerators, closures, or temporary collections.
  • Reusable buffers and pools – pre‑allocated, thread‑safe lists reused across ticks.
  • Manual Span‑based parsing – character‑by‑character processing instead of Split() or regex.
  • No captured closures – avoided delegate allocations wherever possible.
  • Classic for loops – bypassing enumerator overhead.
  • Compact node design – fixed arrays, lightweight structs, minimal fields.

Full DSL and JSON Compatibility

A non‑negotiable requirement: any tree from the original Mistreevous must work unchanged.

root {
    sequence {
        action [CheckHealth]
        selector {
            action [Flee]
            action [Fight]
        }
    }
}

MistreevousSharp supports:

  • Identical SUCCESS / FAILURE / RUNNING semantics
  • Same guard evaluation
  • Subtree references
  • Decorator behavior
  • Execution order

You can literally copy‑paste trees between TypeScript and C# projects.

Project Architecture Overview

The repository mirrors the conceptual structure while making optimizations explicit:

MistreevousSharp/
├── assets/                      # Images and thumbnails
├── example/                     # Full working demo (MyAgent + Program.cs)
├── src/Mistreevous/
│   ├── Agent.cs
│   ├── BehaviourTree.cs         # Core execution engine
│   ├── BehaviourTreeBuilder.cs
│   ├── MDSLDefinitionParser.cs  # Zero‑alloc DSL parser
│   ├── Nodes/
│   │   ├── Composite/   (Sequence, Selector, Parallel, …)
│   │   ├── Decorator/   (Repeat, Retry, Flip, …)
│   │   └── Leaf/        (Action, Condition, Wait)
│   ├── Attributes/              # Guards and callbacks
│   └── Optimizations/          # Zero‑allocation helpers
├── .github/workflows/           # Automated NuGet publish
└── README.md

The Optimizations/ folder is unique to the C# version — it centralizes all performance‑critical tricks that don’t exist in the TS original.

Rough Performance Comparison

OperationOriginal Mistreevous (TS)MistreevousSharp (C#)Improvement
DSL Parsing~0.8 ms~0.35 ms~2.3× faster
Per‑tick executionVariable (GC pauses)Stable ~0.00 msNear‑zero overhead
Allocations per tickMultiple objectsZeroCompletely eliminated

The real gains scale dramatically with entity count.

Usage Example in C#

var definition = @"
root {
    sequence {
        action [CheckHealth]
        selector {
            action [Flee]
            action [Fight]
        }
    }
}";

var agent = new MyAgent(); // Implements your action callbacks

var tree = new BehaviourTree(definition, agent);

while (gameIsRunning)
{
    tree.Step(); // One AI tick — zero allocations
}

See the full example in the repo’s example/ folder.

Where It Shines

Beyond multiplayer servers, MistreevousSharp is perfect for:

  • Unity games (no GC spikes)
  • Godot C# projects
  • Simulations and autonomous agents
  • Robotics and drone control
  • Any system needing modular, readable AI

Final Thoughts

Porting Mistreevous to C# was far more than translation — it required rethinking every allocation, every loop, and every object for the .NET reality. The result is a library that keeps the original’s elegance and compatibility while being truly ready for high‑performance, server‑scale scenarios.

If you’re building anything with complex agent behavior in .NET, give it a try! Feedback, stars, and contributions are very welcome. Let me know if you’ve used behavior trees on the server side — what pains did you hit?

Back to Blog

Related posts

Read more »