HTN Planner reference
This chapter collects the runtime configuration keys, what you can rely on versus must not assume, the troubleshooting table, and an overview of the programmatic API entry points.
Settings Registry
Runtime key prefix: /O3DE/Gems/HtnPlanner/Runtime/ (defaults in the Gem’s Registry/htnplanner.setreg).
| Key | Default | Purpose |
|---|---|---|
GlobalPlanningStepsPerFrame | 2048 | Global per-frame DFS step budget (handed to agents in FIFO rotation) |
MaxDecompositionStepsPerPlan | 4096 | Default per-solve step cap (agents can override) |
DefaultReplanIntervalSeconds | 0.5 | Default opportunistic/continuous replan throttle |
ReplanBackoffInitialSeconds / ReplanBackoffMaxSeconds | 0.5 / 5.0 | Failure backoff envelope |
EnableStats | true | Global statistics toggle for htn_dumpStats |
Compile-time key: /O3DE/Gems/HtnPlanner/Compiler/ComplexityWarnThreshold (V15 complexity warning threshold, default 4096).
The planning budget has two layers: the agent’s
Max Decomposition Stepscaps a single solve, whileGlobalPlanningStepsPerFramecaps the combined per-frame consumption of all agents. The former guards against a single exploding domain, the latter controls frame cost.
Capability limits
You can rely on:
- Deterministic planning, with time-sliced scheduling byte-identical to a one-shot solve;
- Domain hot reload with WorldState migration for same-name, same-type keys;
- Three operator extension channels and Domain Import of zero-parameter compound tasks;
- Built-in Wait / SetWorldState / LogMessage / script bridges; Canvas validation and trace replay.
Do not assume:
- Operators move the entity for you (implement or bridge motion yourself, e.g. via the Navigation3D adapter);
- WorldState syncs across multiplayer clients (the authority owns the agent; operators do their own RPCs);
- A string passed from script becomes a
Stringvalue (default boxing is Tag/Crc32unless the key type is declared String); - Editing a
.htnnoderefreshes an already-open Canvas palette (reopen the document or restart Canvas); - Graphs can be edited node-by-node at runtime (not in v1; edit the source asset → recompile → hot reload is the intended path).
Explicitly out of scope for v1: GOAP / behavior-tree hybrids, multi-agent joint planning, LLM integration, built-in multiplayer WorldState sync.
Troubleshooting
| Symptom | Likely cause | What to try |
|---|---|---|
| Agent never acts | Domain not compiled / Auto Start off / wrong Root Task | Check AssetProcessor; check NoPlan in htn_dumpStats; confirm a Domain is assigned (and it is the .htndomain_compiled product) |
| Always NoPlan | Preconditions never true | Verify Externally Written keys really have sensors writing them; inspect values in the ImGui panel |
| Frequent replans / thrash | WorldState rewritten every frame | Raise Replan Interval; reduce noisy keys |
StepLimitExceeded | Method tree too deep/wide | Fix V15 warnings first; raise Max Decomposition Steps only if genuinely needed |
| Script operator stuck | Completion never reported, or stale token | Call HtnCompleteCurrentOperator with the token received in Begin |
| Import fails V11 | Bad path / non-zero-parameter import / import cycle | Fix the Domain Import (project-relative path, zero-parameter compound task); make sure imported domains compile first |
| Macro fails V10 | Macro references a macro / an unregistered node | Macro subtasks[].node may only be a registered operator or operator-preset id |
Canvas palette stale after .htnnode edit | Registry rescan timing | Reopen the document or restart Canvas; AssetProcessor rebuilds Domains automatically |
| Hitching with many agents | Global planning budget under pressure | Watch per-frame steps in htn_dumpStats; adjust via htn_setPlanningBudget or the Settings Registry |
Programmatic API overview
The planning core is pure C++ with no editor dependencies, callable without components or assets—useful for unit tests, tools, and programmatic domain construction. Public headers live under HtnPlanner/:
| Entry point | Header | Purpose |
|---|---|---|
FindPlan(domainView, worldState, rootTask, settings) | HtnPlanning.h | One-shot solve; returns Plan + MTR + optional trace; byte-identical to runtime incremental planning |
HtnDomainBuilderUtil | HtnDomainBuilderUtil.h | Fluent recording + Build() flattening for programmatic Domains (AddKey / BeginCompound / BeginMethod / AddSubtask / BeginPrimitive / AddCompare / AddEffect / SetRootTask) |
HtnLinkedDomainView | HtnDomain.h | Import-linked domain view; obtain it from an asset via HtnDomainAsset::GetLinkedView() |
IHtnSystem (AZ::Interface) | HtnPlannerBus.h | Global singleton surface: SetGlobalPlanningBudget / GetStats / SetDebugAgent / DumpRecordedTraces |
Minimal example:
using namespace HtnPlanner;
HtnDomainBuilderUtil builder;
builder.AddKey("EnemyVisible", HtnValueType::Bool, HtnValue(false));
builder.BeginCompound("Root");
builder.BeginMethod("Engage");
builder.AddCompare("EnemyVisible", HtnCompareOp::Equal, HtnDomainBuilderUtil::Lit(HtnValue(true)));
builder.AddSubtask("Attack");
builder.SetRootTask("Root");
// . BeginPrimitive / AddEffect .
HtnDomain domain;
AZStd::vector<AZStd::string> errors;
builder.Build(domain, &errors);
HtnLinkedDomainView view(domain);
HtnWorldState state;
state.Initialize(view.GetUnifiedSchema());
state.SetValue(AZ::Crc32("EnemyVisible"), HtnValue(true));
HtnPlanningSettings settings;
const HtnTaskIndex root = domain.FindTaskByName(AZ::Crc32("Root")).value();
HtnPlanResult result = FindPlan(view, state, root, settings);
See the programmatic API section above for the primary builder entry points.
Further reading
- Navigation3D: optional
Nav3D.*operators when both Gems are enabled. - Dawn Stream: remotely review levels that include HTN-driven agents.