Sentinel · Intermediate · Tutorial 2 of 3

The Contract

Sentinel doesn't run your AI — it describes it. Once that clicks, the Blackboard, the Decorator and the version numbers all stop being mysterious, and you stop writing trees that can't possibly work.

AshForge SentinelAfter: First Patrol~30 min

Still no unsaved-changes warning. Same as tutorial one — closing the window discards everything silently. Press Save… before you close, every time.

The idea everything else hangs off

In tutorial one you built a tree and watched agents obey it, which makes it very natural to assume Sentinel is the AI. It isn't, and almost every confusing thing about the tool dissolves once you see what it actually produces.

Export a behaviour and open the file. Every node in it looks like this — and only like this:

{
  "id":       "n7",
  "kind":     "Condition",
  "task":     "CanSeePlayer",
  "children": null
}

Four fields. An identity, a structural kind, the name of a task, and its children. There is no radius, no target, no "how far can it see". CanSeePlayer is a word the game already knows. Sentinel is choosing which words to say and in what order; the game decides what they mean.

Sentinel authors a contract. The game fulfils it. Your tree is a script of task names and control flow. Every behaviour behind those names — what counts as seeing, how patrolling works, how far is "in range" — lives in the game's code, not in your file.

exports fulfils In Sentinel tree shape task names blackboard declarations you author this .behavior.json kind · task · children blackboard[ ] two version numbers the contract In the game what each task DOES reads/writes the variables every actual decision someone else built this
The division of labour. Everything you can do in Sentinel lives in the left panel; everything that makes an NPC behave lives in the right one.

The Blackboard is a declaration, not a memory

Open a behaviour and look at the Blackboard panel on the right — the one I told you to ignore last time. Press + Add variable and you get a row: a name, a type dropdown, a default value, and an × to remove it. Five types are available: Bool, Int, Float, String, Vector3.

Here is the part that trips everyone up. Nothing in the editor connects a variable to a node. You cannot make a Condition test alarmRaised, and no Action can write to it. Declaring a variable does not change what your tree does inside Sentinel, and it never will — that isn't a missing feature so much as the wrong side of the line.

So what is it for? It's the state half of the contract. When your behaviour ships, the game needs to know which variables this tree expects to exist, what type each one is, and what value it starts at. Declaring alarmRaised : Bool = false tells the runtime: allocate this, initialise it to false, and let my tasks use it.

Read it as a function signature. You are not writing the body; you are declaring what the body will be handed.

01 Declare the guard's state

Load Templates → Patrol Guard again, then add three variables:

Save, then export, then open the .behavior.json in a text editor. Your three variables are in there, in a blackboard array, with names, types and defaults intact — sitting alongside the tree rather than inside it. That's the shape of the handoff.

The one piece of logic the editor really does own

If task meanings live in the game, what genuine authoring power do you have? Control flow. Selector and Sequence from tutorial one, plus two more.

02 Decorator — the Inverter

Drag + Decorator from the palette. It defaults to Inverter, and it takes exactly one child — try to give it two and the second connection won't take.

An Inverter flips its child's result: success becomes failure, failure becomes success. That sounds academic until you want a condition you don't have. There is no CannotSeePlayer in the palette — the conditions available are only CanSeePlayer, HealthLow, HasTarget and IsInRange. Wrap CanSeePlayer in an Inverter and you have built its opposite.

Try it: add a Sequence under the Selector, above the Patrol branch. Give it an Inverter wrapping CanSeePlayer, then Wait. Now the guard pauses when it can't see anyone — a nervous sentry rather than a metronome.

03 Parallel — two things at once

+ Parallel runs its children simultaneously rather than in order. Use it when a behaviour genuinely overlaps — walking a route while scanning — instead of forcing an artificial sequence.

Use it sparingly. A Selector or Sequence tells a reader exactly what happens next; a Parallel says "several things, and the result depends on a policy." It is the node most likely to make your tree hard to debug in tutorial three.

Two version numbers, and why one of them protects you

Every exported behaviour carries two:

FieldMeans
behaviorVersionYour version of this behaviour. You set it under Info…. Bump it when you change the tree.
primitiveSetVersionThe version of the vocabulary — the set of task names Sentinel knows. You don't set this; it's stamped for you.

The second one is doing real work. When Crucible loads a behaviour whose primitive set is newer than the one it supports, it says so out loud: "Primitive set v2 is newer than supported v1; unknown tasks will no-op."

That warning is the difference between a bug you can see and a bug you can't. Without it, a tree using a task the runtime doesn't recognise would simply do nothing, in one branch, intermittently — and you would go looking at your tree structure, which would be fine. When you see that warning, stop and check versions before you debug anything else.

Practical habit: set behaviorVersion under Info… before exporting, not after. The value is written into the file at export time, so bumping it afterwards leaves you with a file whose contents disagree with your intent.