BREAKING CHANGES: - Relationship syntax now requires blocks for all participants - Removed self/other perspective blocks from relationships - Replaced 'guard' keyword with 'if' for behavior tree decorators Language Features: - Add tree-sitter grammar with improved if/condition disambiguation - Add comprehensive tutorial and reference documentation - Add SBIR v0.2.0 binary format specification - Add resource linking system for behaviors and schedules - Add year-long schedule patterns (day, season, recurrence) - Add behavior tree enhancements (named nodes, decorators) Documentation: - Complete tutorial series (9 chapters) with baker family examples - Complete reference documentation for all language features - SBIR v0.2.0 specification with binary format details - Added locations and institutions documentation Examples: - Convert all examples to baker family scenario - Add comprehensive working examples Tooling: - Zed extension with LSP integration - Tree-sitter grammar for syntax highlighting - Build scripts and development tools Version Updates: - Main package: 0.1.0 → 0.2.0 - Tree-sitter grammar: 0.1.0 → 0.2.0 - Zed extension: 0.1.0 → 0.2.0 - Storybook editor: 0.1.0 → 0.2.0
884 lines
33 KiB
HTML
884 lines
33 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>Behavior Trees - Storybook Language Guide</title>
|
||
|
||
|
||
<!-- Custom HTML head -->
|
||
|
||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="theme-color" content="#ffffff">
|
||
|
||
<link rel="icon" href="../favicon.svg">
|
||
<link rel="shortcut icon" href="../favicon.png">
|
||
<link rel="stylesheet" href="../css/variables.css">
|
||
<link rel="stylesheet" href="../css/general.css">
|
||
<link rel="stylesheet" href="../css/chrome.css">
|
||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||
|
||
<!-- Fonts -->
|
||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||
|
||
<!-- Highlight.js Stylesheets -->
|
||
<link rel="stylesheet" href="../highlight.css">
|
||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||
|
||
<!-- Custom theme stylesheets -->
|
||
|
||
|
||
<!-- Provide site root to javascript -->
|
||
<script>
|
||
var path_to_root = "../";
|
||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||
</script>
|
||
<!-- Start loading toc.js asap -->
|
||
<script src="../toc.js"></script>
|
||
</head>
|
||
<body>
|
||
<div id="body-container">
|
||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||
<script>
|
||
try {
|
||
var theme = localStorage.getItem('mdbook-theme');
|
||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||
|
||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||
}
|
||
|
||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||
}
|
||
} catch (e) { }
|
||
</script>
|
||
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script>
|
||
var theme;
|
||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||
const html = document.documentElement;
|
||
html.classList.remove('light')
|
||
html.classList.add(theme);
|
||
html.classList.add("js");
|
||
</script>
|
||
|
||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script>
|
||
var sidebar = null;
|
||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||
if (document.body.clientWidth >= 1080) {
|
||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
sidebar = sidebar || 'visible';
|
||
} else {
|
||
sidebar = 'hidden';
|
||
}
|
||
sidebar_toggle.checked = sidebar === 'visible';
|
||
html.classList.remove('sidebar-visible');
|
||
html.classList.add("sidebar-" + sidebar);
|
||
</script>
|
||
|
||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
<!-- populated by js -->
|
||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||
<noscript>
|
||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||
</noscript>
|
||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||
<div class="sidebar-resize-indicator"></div>
|
||
</div>
|
||
</nav>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
<div id="menu-bar-hover-placeholder"></div>
|
||
<div id="menu-bar" class="menu-bar sticky">
|
||
<div class="left-buttons">
|
||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
<i class="fa fa-bars"></i>
|
||
</label>
|
||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||
<i class="fa fa-paint-brush"></i>
|
||
</button>
|
||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||
</ul>
|
||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||
<i class="fa fa-search"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||
|
||
<div class="right-buttons">
|
||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||
<i id="print-button" class="fa fa-print"></i>
|
||
</a>
|
||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||
<i id="git-repository-button" class="fa fa-github"></i>
|
||
</a>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div id="search-wrapper" class="hidden">
|
||
<form id="searchbar-outer" class="searchbar-outer">
|
||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||
</form>
|
||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||
<div id="searchresults-header" class="searchresults-header"></div>
|
||
<ul id="searchresults">
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||
<script>
|
||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||
});
|
||
</script>
|
||
|
||
<div id="content" class="content">
|
||
<main>
|
||
<h1 id="behavior-trees"><a class="header" href="#behavior-trees">Behavior Trees</a></h1>
|
||
<p>Behavior trees define the decision-making logic for characters, institutions, and other entities. They model how an entity chooses actions, responds to conditions, and adapts to its environment. Storybook uses behavior trees for character AI, NPC routines, and complex reactive behavior.</p>
|
||
<h2 id="what-is-a-behavior-tree"><a class="header" href="#what-is-a-behavior-tree">What is a Behavior Tree?</a></h2>
|
||
<p>A behavior tree is a hierarchical structure that executes from root to leaves, making decisions based on success/failure of child nodes:</p>
|
||
<ul>
|
||
<li><strong>Composite nodes</strong> (choose, then) have multiple children and control flow</li>
|
||
<li><strong>Condition nodes</strong> (if, when) test predicates</li>
|
||
<li><strong>Action nodes</strong> execute concrete behaviors</li>
|
||
<li><strong>Decorator nodes</strong> (repeat, invert, timeout, etc.) modify child behavior</li>
|
||
<li><strong>Subtree nodes</strong> (include) reference other behavior trees</li>
|
||
</ul>
|
||
<p>Behavior trees are evaluated every tick (frame), flowing through the tree from root to leaves until a node returns success or failure.</p>
|
||
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><behavior-decl> ::= "behavior" <identifier> <body>
|
||
|
||
<body> ::= "{" <prose-blocks>* <behavior-node> "}"
|
||
|
||
<behavior-node> ::= <selector>
|
||
| <sequence>
|
||
| <condition>
|
||
| <action>
|
||
| <decorator>
|
||
| <subtree>
|
||
|
||
<selector> ::= "choose" <label>? "{" <behavior-node>+ "}"
|
||
|
||
<sequence> ::= "then" <label>? "{" <behavior-node>+ "}"
|
||
|
||
<condition> ::= ("if" | "when") "(" <expression> ")"
|
||
|
||
<action> ::= <identifier> ( "(" <param-list> ")" )?
|
||
|
||
<param-list> ::= <field> ("," <field>)*
|
||
|
||
<decorator> ::= <decorator-type> ("(" <params> ")")? "{" <behavior-node> "}"
|
||
|
||
<subtree> ::= "include" <qualified-path>
|
||
|
||
<label> ::= <identifier>
|
||
</code></pre>
|
||
<h2 id="composite-nodes"><a class="header" href="#composite-nodes">Composite Nodes</a></h2>
|
||
<h3 id="selector-choose"><a class="header" href="#selector-choose">Selector: <code>choose</code></a></h3>
|
||
<p>A selector tries its children in order until one succeeds. Returns success if any child succeeds, failure if all fail.</p>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Try first child</li>
|
||
<li>If succeeds -> return success</li>
|
||
<li>If fails -> try next child</li>
|
||
<li>If all fail -> return failure</li>
|
||
</ol>
|
||
<p><strong>Use case:</strong> “Try A, if that fails try B, if that fails try C…”</p>
|
||
<pre><code class="language-storybook">behavior GuardBehavior {
|
||
choose guard_actions {
|
||
AttackIntruder // Try first
|
||
SoundAlarm // If attack fails, sound alarm
|
||
FleeInPanic // If alarm fails, flee
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Named selectors:</strong></p>
|
||
<pre><code class="language-storybook">choose service_options {
|
||
then serve_regular {
|
||
CustomerIsWaiting
|
||
TakeOrder
|
||
}
|
||
|
||
then restock_display {
|
||
DisplayIsLow
|
||
FetchFromKitchen
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>Labels are optional but recommended for complex trees–they improve readability and debugging.</p>
|
||
<h3 id="sequence-then"><a class="header" href="#sequence-then">Sequence: <code>then</code></a></h3>
|
||
<p>A sequence runs its children in order until one fails. Returns success only if all children succeed, failure if any fails.</p>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Run first child</li>
|
||
<li>If fails -> return failure</li>
|
||
<li>If succeeds -> run next child</li>
|
||
<li>If all succeed -> return success</li>
|
||
</ol>
|
||
<p><strong>Use case:</strong> “Do A, then B, then C… all must succeed”</p>
|
||
<pre><code class="language-storybook">behavior BrewingSequence {
|
||
then brew_potion {
|
||
GatherIngredients // Must succeed
|
||
MixIngredients // Then this must succeed
|
||
Boil // Then this must succeed
|
||
BottlePotion // Finally this
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Named sequences:</strong></p>
|
||
<pre><code class="language-storybook">then prepare_sourdough {
|
||
MixDough
|
||
KneadDough
|
||
ShapeLoaves
|
||
}
|
||
</code></pre>
|
||
<h3 id="nesting-composites"><a class="header" href="#nesting-composites">Nesting Composites</a></h3>
|
||
<p>Composite nodes can nest arbitrarily deep:</p>
|
||
<pre><code class="language-storybook">behavior ComplexAI {
|
||
choose root_decision {
|
||
// Combat branch
|
||
then engage_combat {
|
||
if(enemy_nearby)
|
||
choose combat_tactics {
|
||
AttackWithSword
|
||
AttackWithMagic
|
||
DefendAndWait
|
||
}
|
||
}
|
||
|
||
// Exploration branch
|
||
then explore_area {
|
||
if(safe)
|
||
choose exploration_mode {
|
||
SearchForTreasure
|
||
MapTerritory
|
||
Rest
|
||
}
|
||
}
|
||
|
||
// Default: Idle
|
||
Idle
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="condition-nodes"><a class="header" href="#condition-nodes">Condition Nodes</a></h2>
|
||
<p>Conditions test expressions and return success/failure based on the result.</p>
|
||
<h3 id="if-vs-when"><a class="header" href="#if-vs-when"><code>if</code> vs. <code>when</code></a></h3>
|
||
<p>Both are semantically identical–use whichever reads better in context:</p>
|
||
<pre><code class="language-storybook">behavior Example {
|
||
choose options {
|
||
// "if" for state checks
|
||
then branch_a {
|
||
if(player_nearby)
|
||
Attack
|
||
}
|
||
|
||
// "when" for event-like conditions
|
||
then branch_b {
|
||
when(alarm_triggered)
|
||
Flee
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="condition-syntax"><a class="header" href="#condition-syntax">Condition Syntax</a></h3>
|
||
<pre><code class="language-storybook">if(health < 20) // Comparison
|
||
when(is_hostile) // Boolean field
|
||
if(distance > 10 and has_weapon) // Logical AND
|
||
when(not is_stunned or is_enraged) // Logical OR with NOT
|
||
</code></pre>
|
||
<p>See <a href="./17-expressions.html">Expression Language</a> for complete expression syntax.</p>
|
||
<h2 id="action-nodes"><a class="header" href="#action-nodes">Action Nodes</a></h2>
|
||
<p>Actions are leaf nodes that execute concrete behaviors. They reference action implementations in the runtime.</p>
|
||
<h3 id="basic-actions"><a class="header" href="#basic-actions">Basic Actions</a></h3>
|
||
<pre><code class="language-storybook">behavior SimpleActions {
|
||
then do_things {
|
||
MoveForward
|
||
TurnLeft
|
||
Attack
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="actions-with-parameters"><a class="header" href="#actions-with-parameters">Actions with Parameters</a></h3>
|
||
<p>Actions can have named parameters using parenthesis syntax:</p>
|
||
<pre><code class="language-storybook">behavior ParameterizedActions {
|
||
then patrol {
|
||
MoveTo(destination: "Waypoint1", speed: 1.5)
|
||
WaitFor(duration: 5s)
|
||
MoveTo(destination: "Waypoint2", speed: 1.5)
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Parameter passing:</strong></p>
|
||
<ul>
|
||
<li>Parameters are passed as fields (name: value)</li>
|
||
<li>All <a href="./18-value-types.html">value types</a> supported</li>
|
||
<li>Runtime validates parameter types</li>
|
||
</ul>
|
||
<pre><code class="language-storybook">behavior RichParameters {
|
||
then complex_action {
|
||
CastSpell(spell: "Fireball", target: enemy_position, power: 75, multicast: false)
|
||
Heal(amount: 50, target: self)
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="decorator-nodes"><a class="header" href="#decorator-nodes">Decorator Nodes</a></h2>
|
||
<p>Decorators wrap a single child node and modify its behavior. See <a href="./12-decorators.html">Decorators</a> for complete reference.</p>
|
||
<h3 id="common-decorators"><a class="header" href="#common-decorators">Common Decorators</a></h3>
|
||
<pre><code class="language-storybook">behavior DecoratorExamples {
|
||
choose {
|
||
// Repeat infinitely
|
||
repeat {
|
||
PatrolRoute
|
||
}
|
||
|
||
// Repeat exactly 3 times
|
||
repeat(3) {
|
||
CheckDoor
|
||
}
|
||
|
||
// Repeat 2 to 5 times
|
||
repeat(2..5) {
|
||
SearchArea
|
||
}
|
||
|
||
// Invert success/failure
|
||
invert {
|
||
IsEnemyNearby // Returns success if enemy NOT nearby
|
||
}
|
||
|
||
// Retry up to 5 times on failure
|
||
retry(5) {
|
||
OpenLockedDoor
|
||
}
|
||
|
||
// Timeout after 10 seconds
|
||
timeout(10s) {
|
||
SolveComplexPuzzle
|
||
}
|
||
|
||
// Cooldown: only run once per 30 seconds
|
||
cooldown(30s) {
|
||
FireCannon
|
||
}
|
||
|
||
// If: only run if condition true
|
||
if(health > 50) {
|
||
AggressiveAttack
|
||
}
|
||
|
||
// Always succeed regardless of child result
|
||
succeed_always {
|
||
AttemptOptionalTask
|
||
}
|
||
|
||
// Always fail regardless of child result
|
||
fail_always {
|
||
ImpossipleTask
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="subtree-references"><a class="header" href="#subtree-references">Subtree References</a></h2>
|
||
<p>The <code>include</code> keyword references another behavior tree, enabling modularity and reuse.</p>
|
||
<h3 id="basic-subtree"><a class="header" href="#basic-subtree">Basic Subtree</a></h3>
|
||
<pre><code class="language-storybook">behavior PatrolRoute {
|
||
then patrol {
|
||
MoveTo(destination: "Waypoint1")
|
||
MoveTo(destination: "Waypoint2")
|
||
MoveTo(destination: "Waypoint3")
|
||
}
|
||
}
|
||
|
||
behavior GuardBehavior {
|
||
choose guard_ai {
|
||
then combat {
|
||
if(enemy_nearby)
|
||
include combat::engage
|
||
}
|
||
|
||
include PatrolRoute // Reuse patrol behavior
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="qualified-subtree-references"><a class="header" href="#qualified-subtree-references">Qualified Subtree References</a></h3>
|
||
<pre><code class="language-storybook">behavior ComplexAI {
|
||
choose ai_modes {
|
||
include behaviors::combat::melee
|
||
include behaviors::combat::ranged
|
||
include behaviors::exploration::search
|
||
include behaviors::social::greet
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="named-nodes"><a class="header" href="#named-nodes">Named Nodes</a></h2>
|
||
<p>Both composite nodes (<code>choose</code>, <code>then</code>) can have optional labels:</p>
|
||
<pre><code class="language-storybook">behavior NamedNodeExample {
|
||
choose daily_priority { // Named selector
|
||
then handle_special_orders { // Named sequence
|
||
CheckOrderQueue
|
||
PrepareSpecialIngredients
|
||
BakeSpecialItem
|
||
}
|
||
|
||
choose regular_work { // Named selector
|
||
then morning_bread { // Named sequence
|
||
MixSourdough
|
||
BakeLoaves
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Benefits:</strong></p>
|
||
<ul>
|
||
<li><strong>Readability</strong>: Tree structure is self-documenting</li>
|
||
<li><strong>Debugging</strong>: Named nodes appear in execution traces</li>
|
||
<li><strong>Narrative</strong>: Labels can be narrative (“handle_special_orders” vs. anonymous node)</li>
|
||
</ul>
|
||
<p><strong>Guidelines:</strong></p>
|
||
<ul>
|
||
<li>Use labels for important structural nodes</li>
|
||
<li>Use descriptive, narrative names</li>
|
||
<li>Omit labels for trivial nodes</li>
|
||
</ul>
|
||
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
|
||
<h3 id="simple-guard-ai"><a class="header" href="#simple-guard-ai">Simple Guard AI</a></h3>
|
||
<pre><code class="language-storybook">behavior GuardPatrol {
|
||
choose guard_logic {
|
||
// Combat if enemy nearby
|
||
then combat_mode {
|
||
if(enemy_nearby and has_weapon)
|
||
Attack
|
||
}
|
||
|
||
// Sound alarm if see intruder
|
||
then alarm_mode {
|
||
if(intruder_detected)
|
||
SoundAlarm
|
||
}
|
||
|
||
// Default: Patrol
|
||
then patrol_mode {
|
||
include PatrolRoute
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="complex-npc-behavior"><a class="header" href="#complex-npc-behavior">Complex NPC Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Innkeeper_DailyRoutine {
|
||
---description
|
||
The innkeeper's behavior throughout the day. Uses selectors for
|
||
decision-making and sequences for multi-step actions.
|
||
---
|
||
|
||
choose daily_activity {
|
||
// Morning: Prepare for opening
|
||
then morning_prep {
|
||
when(time_is_morning)
|
||
then prep_sequence {
|
||
UnlockDoor
|
||
LightFireplace
|
||
PrepareBreakfast
|
||
WaitForGuests
|
||
}
|
||
}
|
||
|
||
// Day: Serve customers
|
||
then day_service {
|
||
when(time_is_daytime)
|
||
choose service_mode {
|
||
// Serve customer if present
|
||
then serve {
|
||
if(customer_waiting)
|
||
GreetCustomer
|
||
TakeOrder
|
||
ServeMeal
|
||
CollectPayment
|
||
}
|
||
|
||
// Restock if needed
|
||
then restock {
|
||
if(inventory_low)
|
||
ReplenishInventory
|
||
}
|
||
|
||
// Default: Clean
|
||
CleanTables
|
||
}
|
||
}
|
||
|
||
// Night: Close up
|
||
then evening_close {
|
||
when(time_is_evening)
|
||
then close_sequence {
|
||
DismissRemainingGuests
|
||
LockDoor
|
||
CountMoney
|
||
GoToBed
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="morning-baking-routine"><a class="header" href="#morning-baking-routine">Morning Baking Routine</a></h3>
|
||
<pre><code class="language-storybook">behavior Baker_MorningRoutine {
|
||
---description
|
||
Martha's morning routine: prepare dough step by step,
|
||
from mixing to shaping to baking.
|
||
---
|
||
|
||
then morning_baking {
|
||
// Start with sourdough
|
||
then prepare_starter {
|
||
CheckStarter
|
||
FeedStarter
|
||
WaitForActivity
|
||
}
|
||
|
||
// Mix the dough
|
||
then mix_dough {
|
||
MeasureFlour
|
||
AddWater
|
||
IncorporateStarter
|
||
}
|
||
|
||
// Knead and shape
|
||
then shape_loaves {
|
||
KneadDough
|
||
FirstRise
|
||
ShapeLoaves
|
||
}
|
||
|
||
// Bake
|
||
then bake {
|
||
PreHeatOven
|
||
LoadLoaves
|
||
MonitorBaking
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="repeating-behavior"><a class="header" href="#repeating-behavior">Repeating Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Bakery_CustomerServiceLoop {
|
||
---description
|
||
The bakery's continuous customer service loop. Uses infinite
|
||
repeat decorator to serve customers throughout the day.
|
||
---
|
||
|
||
repeat {
|
||
then service_cycle {
|
||
// Check for customers
|
||
choose service_mode {
|
||
then serve_waiting {
|
||
if(customer_waiting)
|
||
GreetCustomer
|
||
TakeOrder
|
||
}
|
||
|
||
then restock_display {
|
||
if(display_low)
|
||
FetchFromKitchen
|
||
ArrangeOnShelves
|
||
}
|
||
}
|
||
|
||
// Process payment
|
||
CollectPayment
|
||
ThankCustomer
|
||
|
||
// Brief pause between customers
|
||
timeout(5s) {
|
||
CleanCounter
|
||
}
|
||
|
||
PrepareForNextCustomer
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="retry-logic-for-delicate-recipes"><a class="header" href="#retry-logic-for-delicate-recipes">Retry Logic for Delicate Recipes</a></h3>
|
||
<pre><code class="language-storybook">behavior Baker_DelicatePastry {
|
||
---description
|
||
Elena attempting a delicate pastry recipe that requires
|
||
precise technique. Uses retry decorator for persistence.
|
||
---
|
||
|
||
choose baking_strategy {
|
||
// Try when conditions are right
|
||
then attempt_pastry {
|
||
if(oven_at_temperature)
|
||
// Try up to 3 times
|
||
retry(3) {
|
||
then make_pastry {
|
||
RollDoughThin
|
||
ApplyFilling
|
||
FoldAndSeal
|
||
CheckForLeaks
|
||
}
|
||
}
|
||
}
|
||
|
||
// If oven not ready, prepare meanwhile
|
||
then prepare_meanwhile {
|
||
if(not oven_at_temperature)
|
||
then prep_sequence {
|
||
PrepareIngredients
|
||
MixFilling
|
||
ChillDough
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="integration-with-characters"><a class="header" href="#integration-with-characters">Integration with Characters</a></h2>
|
||
<p>Characters link to behaviors using the <code>uses behaviors</code> clause:</p>
|
||
<pre><code class="language-storybook">character Guard {
|
||
uses behaviors: [
|
||
{
|
||
tree: guards::patrol_route
|
||
priority: normal
|
||
},
|
||
{
|
||
tree: guards::engage_hostile
|
||
when: threat_detected
|
||
priority: high
|
||
},
|
||
{
|
||
tree: guards::sound_alarm
|
||
when: emergency
|
||
priority: critical
|
||
}
|
||
]
|
||
}
|
||
</code></pre>
|
||
<p><strong>Behavior selection:</strong></p>
|
||
<ul>
|
||
<li>Multiple behaviors evaluated by priority (critical > high > normal > low)</li>
|
||
<li>Conditions (<code>when</code> clause) gate behavior activation</li>
|
||
<li>Higher-priority behaviors preempt lower-priority ones</li>
|
||
</ul>
|
||
<p>See <a href="./10-characters.html#behavior-integration">Characters</a> for complete behavior linking syntax.</p>
|
||
<h2 id="execution-semantics"><a class="header" href="#execution-semantics">Execution Semantics</a></h2>
|
||
<h3 id="tick-based-evaluation"><a class="header" href="#tick-based-evaluation">Tick-Based Evaluation</a></h3>
|
||
<p>Behavior trees execute every “tick” (typically once per frame):</p>
|
||
<ol>
|
||
<li><strong>Start at root</strong> node</li>
|
||
<li><strong>Traverse</strong> down to leaves based on composite logic</li>
|
||
<li><strong>Execute</strong> leaf nodes (conditions, actions)</li>
|
||
<li><strong>Return</strong> success/failure up the tree</li>
|
||
<li><strong>Repeat</strong> next tick</li>
|
||
</ol>
|
||
<h3 id="node-return-values"><a class="header" href="#node-return-values">Node Return Values</a></h3>
|
||
<p>Every node returns one of:</p>
|
||
<ul>
|
||
<li><strong>Success</strong>: Node completed successfully</li>
|
||
<li><strong>Failure</strong>: Node failed</li>
|
||
<li><strong>Running</strong>: Node still executing (async action)</li>
|
||
</ul>
|
||
<h3 id="stateful-vs-stateless"><a class="header" href="#stateful-vs-stateless">Stateful vs. Stateless</a></h3>
|
||
<ul>
|
||
<li><strong>Stateless nodes</strong>: Evaluate fresh every tick (conditions, simple actions)</li>
|
||
<li><strong>Stateful nodes</strong>: Maintain state across ticks (decorators, long actions)</li>
|
||
</ul>
|
||
<p>Example of stateful behavior:</p>
|
||
<pre><code class="language-storybook">behavior LongAction {
|
||
timeout(30s) { // Stateful: tracks elapsed time
|
||
ComplexCalculation // May take multiple ticks
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>At least one node</strong>: Behavior body must contain at least one node</li>
|
||
<li><strong>Composite children</strong>: <code>choose</code> and <code>then</code> require at least one child</li>
|
||
<li><strong>Decorator child</strong>: Decorators require exactly one child</li>
|
||
<li><strong>Action exists</strong>: Action names must reference registered actions in runtime</li>
|
||
<li><strong>Subtree exists</strong>: <code>include</code> must reference a defined <code>behavior</code> declaration</li>
|
||
<li><strong>Expression validity</strong>: Condition expressions must be well-formed</li>
|
||
<li><strong>Duration format</strong>: Decorator durations must be valid (e.g., <code>5s</code>, <code>10m</code>)</li>
|
||
<li><strong>Unique labels</strong>: Node labels (if used) should be unique within their parent</li>
|
||
<li><strong>Parameter types</strong>: Action parameters must match expected types</li>
|
||
</ol>
|
||
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
|
||
<h3 id="1-prefer-shallow-trees"><a class="header" href="#1-prefer-shallow-trees">1. Prefer Shallow Trees</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">choose {
|
||
then { then { then { then { Action } } } } // Too deep!
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">choose root {
|
||
include combat_tree
|
||
include exploration_tree
|
||
include social_tree
|
||
}
|
||
</code></pre>
|
||
<h3 id="2-use-named-nodes-for-clarity"><a class="header" href="#2-use-named-nodes-for-clarity">2. Use Named Nodes for Clarity</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">choose {
|
||
then {
|
||
IsHungry
|
||
FindFood
|
||
Eat
|
||
}
|
||
Wander
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">choose survival {
|
||
then eat_if_hungry {
|
||
IsHungry
|
||
FindFood
|
||
Eat
|
||
}
|
||
Wander
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-subtrees-for-reusability"><a class="header" href="#3-subtrees-for-reusability">3. Subtrees for Reusability</a></h3>
|
||
<p><strong>Avoid:</strong> Duplicating logic across behaviors</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">behavior Combat_Common {
|
||
then attack_sequence {
|
||
DrawWeapon
|
||
Approach
|
||
Strike
|
||
}
|
||
}
|
||
|
||
behavior Warrior {
|
||
include Combat_Common
|
||
}
|
||
|
||
behavior Guard {
|
||
include Combat_Common
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-decorators-for-timing"><a class="header" href="#4-decorators-for-timing">4. Decorators for Timing</a></h3>
|
||
<p><strong>Avoid:</strong> Manual timing in actions</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">timeout(10s) {
|
||
ComplexTask
|
||
}
|
||
|
||
cooldown(30s) {
|
||
SpecialAbility
|
||
}
|
||
</code></pre>
|
||
<h3 id="5-guard-for-preconditions"><a class="header" href="#5-guard-for-preconditions">5. Guard for Preconditions</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">then problematic {
|
||
ExpensiveAction // Always runs even if inappropriate
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">if(can_afford_action) {
|
||
ExpensiveAction // Only runs when condition passes
|
||
}
|
||
</code></pre>
|
||
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="./12-decorators.html">Decorators</a> - Complete decorator reference</li>
|
||
<li><a href="./10-characters.html">Characters</a> - Linking behaviors to characters</li>
|
||
<li><a href="./17-expressions.html">Expression Language</a> - Condition expression syntax</li>
|
||
<li><a href="./18-value-types.html">Value Types</a> - Parameter value types</li>
|
||
<li><a href="../advanced/20-patterns.html">Design Patterns</a> - Common behavior tree patterns</li>
|
||
</ul>
|
||
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Reactive AI</strong>: Behavior trees continuously react to changing conditions</li>
|
||
<li><strong>Hierarchical decision-making</strong>: Composite nodes create decision hierarchies</li>
|
||
<li><strong>Modularity</strong>: Subtrees enable behavior reuse and composition</li>
|
||
<li><strong>Narrative-driven design</strong>: Named nodes make behavior trees readable as stories</li>
|
||
</ul>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
<a rel="prev" href="../reference/10-characters.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next prefetch" href="../reference/12-decorators.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
<a rel="prev" href="../reference/10-characters.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next prefetch" href="../reference/12-decorators.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
<!-- Livereload script (if served using the cli tool) -->
|
||
<script>
|
||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||
const socket = new WebSocket(wsAddress);
|
||
socket.onmessage = function (event) {
|
||
if (event.data === "reload") {
|
||
socket.close();
|
||
location.reload();
|
||
}
|
||
};
|
||
|
||
window.onbeforeunload = function() {
|
||
socket.close();
|
||
}
|
||
</script>
|
||
|
||
|
||
|
||
<script>
|
||
window.playground_copyable = true;
|
||
</script>
|
||
|
||
|
||
<script src="../elasticlunr.min.js"></script>
|
||
<script src="../mark.min.js"></script>
|
||
<script src="../searcher.js"></script>
|
||
|
||
<script src="../clipboard.min.js"></script>
|
||
<script src="../highlight.js"></script>
|
||
<script src="../book.js"></script>
|
||
|
||
<!-- Custom JS scripts -->
|
||
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|