Performance

The numbers behind the 2000-agent claim, with reproduction steps so anyone can verify.

[ Performance ]

2000 agents, single thread, sub-millisecond

Defensible baseline numbers and the exact steps to reproduce them. The data is from the reference workstation; consumer CPUs vary by 2–3×, so re-run on your own hardware and add a row.

p99 < 0.5 ms @ 2000Single-threadReproducible

Headline numbers

Per-cycle wall time for one full replan pass across N agents, plus per-tick dispatcher cost at the same N.

Agent countReplan p99 (ms)Per-agent p99 (µs)Dispatcher tick p99 (ms)
1000.0300.300.0022
2500.0740.300.0053
5000.1300.260.0065
10000.4000.400.023
20000.3750.190.052
40000.8690.220.066

At 2000 agents, replan + dispatcher together stay well under 1 ms per frame on a single thread. Past the 2000 budget the scaling stays roughly linear: 4000 agents land at 0.87 ms, in line with an O(N) loop.

Reference machine

ItemValue
OSWindows 11 Pro 10.0.26100
UE version5.7
Build configDevelopment Editor
RHINullRHI (headless automation)

Benchmark catalogue

Automation tests live under the IntentForge.Performance.* namespace.

TestWhat it measuresAsserts?
SingleStepPlanTrivial archetype planner costmean < 0.1 ms
BranchingArchetypeBranching A* search costmean < 0.5 ms
MediumArchetype20-action linear chainmean < 2.0 ms
ActivityHistoryRingAppendOne RecordActivity at depths 32/128/256depth-256 mean < 0.005 ms
AgentMemoryFootprintKB per active component at N=1/100/1000
ManyAgentReplanPer-cycle wall time for N agents to each replanN=100 p99 < 50 ms
PlannerOnlyReplanSame N sweep, direct Plan() calls
AntiFlapSelectionCostMomentum + filter + latch overhead vs baseline
WorldStateSetScalarCostPer-call SetScalar cost across 4 configs
DispatcherTickCostOne dispatcher Tick(0.016) with N components
SensorSampleCostPer-class Sample() cost (Core sensors only)

Source: Plugins/IntentForge/Source/IntentForgeCore/Private/Tests/PerfBenchmarks_*.cpp.

Reproduce locally

Build the editor target.

"C:\Program Files\Epic Games\UE_5.7\Engine\Build\BatchFiles\Build.bat" \
  CrucibleEditor Win64 Development -Project="<path>\Crucible.uproject" -WaitMutex

Run the perf subset.

"C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" \
  "<path>\Crucible.uproject" \
  -ExecCmds="Automation RunTests IntentForge.Performance; Quit" \
  -unattended -nopause -nullrhi

Drop .Performance to run the full IntentForge automation suite.

Compare against the checked-in baseline.

CSV output: <ProjectDir>/Saved/Logs/IntentForgePerf/perf_<UTC-timestamp>.csv — one row per (benchmark, config, run). Treat PerformanceBaseline.json as the source of truth; the tables above are the human-readable view.

A full sweep takes 10–30 seconds on a developer machine.

Anti-flap stack overhead

The anti-flap stack adds runtime cost on every replan and on every scalar fact write. The AntiFlapSelectionCost benchmark isolates the momentum dimension by running the same archetype with GoalMomentumBonus = 0, then with the default 0.15.

At N=500, 1000 iterations per config, 3 runs:

ConfigurationMean per cycle (ms)p99 per cycle (ms)
Momentum off0.07980.083
Momentum on0.07800.084
Delta-0.0018 (-2.3%)+0.001

The delta sits inside measurement noise. The momentum bonus is effectively free — one multiplication per candidate goal during ranking, lost in the rest of the selection path.

EMA filter and Schmitt latch costs live inside FWorldState::SetScalar and only fire when the schema configures them. The WorldStateSetScalarCost micro-benchmark covers those directly across four configs (baseline / filter-only / latch-only / both) at K=100 batched calls per timing window.

Memory footprint

AgentMemoryFootprint snapshots process memory before and after spawning N components on a transient world, force-GCs, snapshots again, and divides the delta by N.

NTotal delta (KB)Per-agent (KB)
18.08.0
100~0(noise)
10002200.22

The N=100 row falls to zero because process memory snapshots vary by tens of KB between quiescent reads. N=1000 gives the stable estimate: roughly 220 bytes per agent including the component, the live FWorldState, and two history ring buffers at default depths.

Plan on "under 1 KB per agent for the framework data" plus whatever your sensors and executors carry.

Sensor cost

Per-Core-sensor cost for one Sample() call, batched K=100 per timing window across 5000 windows.

Sensor classMean (µs)p99 (µs)
GameplayTagOnActor0.0340.065
DistanceToWorldLocation0.0550.095
DistanceToReferencedActor0.0730.125
LineOfSightToActor0.2080.357

LineOfSightToActor does an actual line trace. The empty-scene case above is best case — expect 2–5× in a dense scene with collision geometry.

Perception sensors (AIPerception, BlackboardBool/Float, EnvQuery) are excluded by design: their cost is dominated by the AI subsystems they bridge into, not the sensor pattern itself.

Methodology

  • Warmup: 10 iterations per config before timing.
  • Repeats: 1000 iterations per config (100 at N ≥ 2000). Multi-agent and dispatcher benchmarks do 3 runs and report median p99.
  • Percentiles: nearest-rank, no interpolation.
  • Lead statistic: p99. A mean that hides a 10× spike every 100 frames is a hung-frame problem.
  • GC: full purge between runs.
  • Timer: FPlatformTime::Seconds() (Windows QPC ≈ 100 ns resolution). Sub-microsecond benchmarks batch K=100 calls per window to clear the noise floor.

Caveats

  • Single-thread. No async path. The numbers are the full game-thread cost.
  • No movement / animation / nav in the test worlds. Real games carry per-actor cost outside the framework.
  • ManyAgentReplan measures per-cycle wall time, not per-plan compute. Coalescing and plan reuse mean the per-cycle work is less than "plan from scratch per agent" — which matches realistic production.
  • AgentMemoryFootprint uses process-global stats. Noisy at the tens-of-KB level — treat as order-of-magnitude.
  • CPU variance is real. Consumer CPUs differ by 2–3× single-threaded. Your numbers will differ; the shape (linear, bounded tail) is what to look for.

Community runs

Re-run the benchmarks on your own hardware and add a row via PR. If you drop the CSV alongside, the project keeps a per-machine record.

ContributorCPURAMOSUEp99 replan @ N=2000CSV
your row heree.g. Ryzen 7 7800X3D32 GB DDR5-6000Win 115.70.41 mslink
◢ Next

Try this next

On this page