Files
storybook/docs/book/tutorial/04-making-characters-act.html
Sienna Meridian Satterwhite 16deb5d237 release: Storybook v0.2.0 - Major syntax and features update
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
2026-02-13 21:52:03 +00:00

454 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Making Characters Act - 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="making-characters-act"><a class="header" href="#making-characters-act">Making Characters Act</a></h1>
<p>In the previous chapter, you created behavior trees with selectors and sequences. Now you will add conditions, action parameters, and decorators to create dynamic, responsive behaviors.</p>
<h2 id="conditions-if-and-when"><a class="header" href="#conditions-if-and-when">Conditions: if and when</a></h2>
<p>Conditions let behavior trees react to the world. Use <code>if</code> or <code>when</code> to test a condition before proceeding:</p>
<pre><code class="language-storybook">behavior Martha_React {
choose response {
then bake_path {
if(inventory_sufficient)
StartBaking
}
then restock_path {
if(inventory_low)
OrderSupplies
}
CleanWorkstation
}
}
</code></pre>
<p><code>if(inventory_sufficient)</code> succeeds when inventory is sufficient, and fails otherwise. If it fails, the entire <code>bake_path</code> sequence fails, and the tree moves on to the next option.</p>
<p><code>if</code> and <code>when</code> are interchangeable use whichever reads more naturally:</p>
<pre><code class="language-storybook">// "if" for state checks
if(health &lt; 20)
// "when" for event-like conditions
when(alarm_triggered)
</code></pre>
<h3 id="condition-expressions"><a class="header" href="#condition-expressions">Condition Expressions</a></h3>
<p>Conditions support comparisons and logical operators:</p>
<pre><code class="language-storybook">// Comparisons
if(health &lt; 20)
if(distance &gt; 100)
if(name == "Martha")
if(status is Curious) // 'is' is syntactic sugar for ==
// Logical operators
if(hungry and tired)
if(rich or lucky)
if(not is_dangerous)
// Combined
if(health &lt; 50 and not has_potion)
if((age &gt; 18 and age &lt; 65) or is_veteran)
</code></pre>
<h2 id="action-parameters"><a class="header" href="#action-parameters">Action Parameters</a></h2>
<p>Actions can take named parameters using parenthesis syntax:</p>
<pre><code class="language-storybook">behavior Martha_BakeSpecial {
then baking {
MixDough(recipe: "sourdough", quantity: 10)
KneadDough(duration: 15m)
BakeLoaves(temperature: 230, duration: 35m)
}
}
</code></pre>
<p>Parameters are fields inside <code>( )</code> after the action name. They let you customize behavior without defining separate actions for each variation.</p>
<h2 id="decorators"><a class="header" href="#decorators">Decorators</a></h2>
<p>Decorators wrap a single child node and modify its behavior. They are your tools for timing, repetition, and conditional execution.</p>
<h3 id="repeat--looping"><a class="header" href="#repeat--looping">repeat Looping</a></h3>
<pre><code class="language-storybook">// Infinite repeat (checks oven forever)
repeat {
CheckOvenTemperature
}
// Repeat exactly 3 times
repeat(3) {
KneadDough
}
// Repeat between 2 and 5 times (random)
repeat(2..5) {
FoldDough
}
</code></pre>
<h3 id="invert--flip-results"><a class="header" href="#invert--flip-results">invert Flip Results</a></h3>
<p>Inverts success/failure. Useful for “if NOT” conditions:</p>
<pre><code class="language-storybook">behavior SafeBake {
choose options {
then bake_safely {
invert { OvenOverheating } // Succeeds if oven is NOT overheating
ContinueBaking
}
StopAndInspect
}
}
</code></pre>
<h3 id="retry--try-again-on-failure"><a class="header" href="#retry--try-again-on-failure">retry Try Again on Failure</a></h3>
<p>Retries the child up to N times if it fails:</p>
<pre><code class="language-storybook">retry(3) {
LightOven // Try up to 3 times before giving up
}
</code></pre>
<h3 id="timeout--time-limits"><a class="header" href="#timeout--time-limits">timeout Time Limits</a></h3>
<p>Fails the child if it does not complete within the duration:</p>
<pre><code class="language-storybook">timeout(10s) {
WaitForDoughToRise // Must finish within 10 seconds
}
</code></pre>
<h3 id="cooldown--rate-limiting"><a class="header" href="#cooldown--rate-limiting">cooldown Rate Limiting</a></h3>
<p>Prevents the child from running again within the cooldown period:</p>
<pre><code class="language-storybook">cooldown(30s) {
CheckOvenTemperature // Can only check once every 30 seconds
}
</code></pre>
<h3 id="if-as-decorator-guard"><a class="header" href="#if-as-decorator-guard">if as Decorator (Guard)</a></h3>
<p>The <code>if</code> decorator only runs the child when a condition is true:</p>
<pre><code class="language-storybook">if(has_special_orders) {
PrepareSpecialBatch // Only prepare when there are orders
}
</code></pre>
<p>This is different from <code>if</code> as a condition node. As a decorator, <code>if</code> wraps a child and gates its execution. As a condition node, <code>if</code> is a simple pass/fail check inline in a sequence.</p>
<h3 id="succeed_always-and-fail_always"><a class="header" href="#succeed_always-and-fail_always">succeed_always and fail_always</a></h3>
<p>Force a result regardless of the child:</p>
<pre><code class="language-storybook">// Try bonus task, but don't fail the routine if it fails
succeed_always {
ExperimentWithNewRecipe
}
// Temporarily disable a feature
fail_always {
UntestedBakingMethod
}
</code></pre>
<h2 id="combining-decorators"><a class="header" href="#combining-decorators">Combining Decorators</a></h2>
<p>Decorators can nest for complex control:</p>
<pre><code class="language-storybook">behavior ResilientAction {
// Only run if oven is ready, with 20s timeout, retrying up to 3 times
if(oven_ready) {
timeout(20s) {
retry(3) {
BakeDelicateItem
}
}
}
}
</code></pre>
<p>Execution flows outside-in: first the <code>if</code> checks the oven, then the timeout starts, then the retry begins.</p>
<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 reuse:</p>
<pre><code class="language-storybook">behavior SourdoughRecipe {
then sourdough {
MixDough(recipe: "sourdough", quantity: 10)
KneadDough(duration: 15m)
FirstRise(duration: 2h)
ShapeLoaves
}
}
behavior Martha_DailyRoutine {
choose daily_priority {
then special_orders {
if(has_special_orders)
include SpecialOrderBehavior
}
include SourdoughRecipe // Reuse sourdough behavior
}
}
</code></pre>
<p>Subtrees help you avoid duplicating behavior logic. You can also reference behaviors from other modules using qualified paths:</p>
<pre><code class="language-storybook">include behaviors::baking::sourdough
include behaviors::service::greet_customer
</code></pre>
<h2 id="behavior-linking-with-priorities"><a class="header" href="#behavior-linking-with-priorities">Behavior Linking with Priorities</a></h2>
<p>Characters can link to multiple behaviors with priorities and conditions:</p>
<pre><code class="language-storybook">character Martha: Human {
uses behaviors: [
{
tree: BakerRoutine
priority: normal
},
{
tree: HandleEmergency
when: emergency_detected
priority: high
},
{
tree: HandleHealthInspection
when: inspector_present
priority: critical
}
]
}
</code></pre>
<p>The runtime evaluates behaviors by priority (critical &gt; high &gt; normal &gt; low). Higher-priority behaviors preempt lower-priority ones when their conditions are met.</p>
<h2 id="a-complete-example"><a class="header" href="#a-complete-example">A Complete Example</a></h2>
<p>Here is a complete behavior tree for handling the morning rush:</p>
<pre><code class="language-storybook">behavior MorningRush_Routine {
---description
The bakery's morning rush routine: serve customers, restock,
and keep the ovens running.
---
repeat {
then rush_cycle {
// Serve any waiting customers
choose service_mode {
then serve_regular {
if(customer_waiting)
GreetCustomer
TakeOrder
PackageItems
CollectPayment
}
then restock {
if(display_low)
FetchFromKitchen
ArrangeOnShelves
}
}
// Check ovens between customers
timeout(5s) {
CheckAllOvens
}
PrepareNextBatch
}
}
}
</code></pre>
<p>This tree repeats forever: serve a customer or restock the display, check the ovens, and prepare the next batch.</p>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>You now know the full toolkit for behavior trees. In <a href="./05-advanced-behaviors.html">Advanced Behaviors</a>, you will learn patterns for building complex AI systems with nested trees, state-based switching, and modular design.</p>
<hr />
<p><strong>Reference</strong>: See <a href="../reference/12-decorators.html">Decorators Reference</a> for all decorator types and <a href="../reference/17-expressions.html">Expression Language</a> for complete condition syntax.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/03-first-behavior-tree.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="../tutorial/05-advanced-behaviors.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="../tutorial/03-first-behavior-tree.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="../tutorial/05-advanced-behaviors.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>