Behaviour guide
A species is driven by one behaviour program: either a state
machine (kind: "fsm") or a behaviour tree
(kind: "bt"). Both share one vocabulary of predicates
(conditions) and actions, defined in
schema/v0.1/behavior-program.schema.json. Most species should
reach for one of the three shared programs in behaviors/
(shown in full below) and override its params; write a
species-local behavior.json only when none of them fit.
Finite state machines
One state is active at a time. Each tick:
-
Every transition is checked in priority order, highest first; ties keep
the order they appear in the file.
from: "*"matches any current state. The first transition whosewhenpredicate holds fires — at most one transition per tick — and the machine moves to itstostate. -
A state's
min_dwell_sblocks any transition out of it whosepriorityis≤ 0(the default, when a transition has nopriority) until that many seconds have passed in the state. Transitions withpriority > 0(for example, fleeing a threat) can always interrupt, dwell time or not. -
Then the now-active state's
actionruns for this tick. If an action fails (its target vanishes, its resource is gone), the machine returns toinitialrather than staying stuck.
Behaviour trees
The whole tree is ticked from the root every tick — there is
no persistent "current node" — so a higher branch can always interrupt a
lower one the moment its condition becomes true.
selector: ticks children in order until one succeeds or is still running; used to pick the highest-priority applicable branch.sequence: ticks children in order until one fails. Acondchild is re-checked every tick, not just once on entry, so a sequence aborts immediately if an earlier condition stops holding.parallel: ticks every child every tick; succeeds oncesucceed_onof them have succeeded.inverter: succeeds if its child fails, and vice versa.succeeder: always succeeds, whatever its child does.repeat: runs its child up torepeattimes.cooldown_s: lets its child start at most once per that many seconds.timeout_s: fails its child if it has been running longer than that many seconds.cond: succeeds if its predicate holds, else fails; carries no action.act: runs one action.subtree: inlines anotherbtprogram'srootby id.
Parameters and ${param}
A program's params block declares named values. Anywhere
else in the program, a string that is exactly
${name} — the whole string, not a substring inside a longer
one — is replaced by that param's value at load time. A species'
behavior.params may override a value for any param the
program declares; it cannot introduce new params the program doesn't
know about. A seasonal.phases[].behavior_params entry can
further override params only while that phase is active.
Lint rules
The validator statically checks every program once for its own shape
(with its declared defaults), then again for each species that uses it,
with that species' behavior.params overrides substituted
in:
- Every node and action type is one the schema knows.
-
Every
${param}reference names a param the program actually declares (UndeclaredParamRef), and is written exactly as${name}, not embedded in a longer string (BadParamRef). -
Every param a program declares in its own
paramsblock is referenced somewhere in that same program's own body, or it's a warning (UnusedParam); asubtreeusing the name doesn't count. -
Numeric fields are range-checked after substitution:
durations (
min_dwell_s,cooldown_s,timeout_s) in (0, 86400] seconds,chanceand aneed'sgt/ltin [0, 1], every*_within_mdistance in (0, 1000],month_inentries in 1..12,sun_elevation_between_degin [-90, 90] (low ≤ high),temperature_between_cin [-60, 60] (low ≤ high), arepeatcount a positive integer, and aparallel'ssucceed_onwithin 1..(number of children). -
FSM:
initialmust name a real state (FsmInitialMissing); a transition'sfrom(or the wildcard"*", which matches every state) andtomust name a real state (FsmUnknownState);from==tonever changes state (FsmNoOpTransition, warning). Every state must be reachable frominitial(FsmUnreachableState) and must have some path — through its own transitions or afrom: "*"one — back toinitial(FsmDeadEnd, error: a true dead end, since nothing can ever return the machine to its start). A state with no transition of its own (only reachable escape routes are wildcard ones defined elsewhere) is a warning, not an error (FsmNoExplicitExit) — a global rule such as fleeing a threat may still be enough to move it on. -
BT: a selector child placed after an unconditional durative act (like
idleorwander, which never yields) is unreachable (BtUnreachableAfterDurative, warning); the root selector should end in such an always-available fallback (BtMissingFallback, warning); a durative act must be the last child of itssequence, since nothing after it would ever run (BtDurativeNotLast, warning). -
subtreereferences must resolve to a realbtprogram id (BtUnresolvedSubtree,BtSubtreeNotBt), with no cycles (BtSubtreeCycle). - An
emoteaction needs akind(EmoteMissingKind). -
No unknown
callid — a species-instantiatedsing/alarmaction'scallmust name acall_idthe species actually declares invocalizations(UnknownCallId, warning). Likewise aneat_nearestaction'stag, and anyfood_within_mpredicate'stag, should name a resource tag the species declares inhabitat.resources(UnknownFoodTag, warning).
Predicate vocabulary
Loading…