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
This commit is contained in:
2026-02-13 21:52:03 +00:00
parent 80332971b8
commit 16deb5d237
290 changed files with 90316 additions and 5827 deletions

View File

@@ -0,0 +1,327 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Welcome to Storybook - 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="welcome-to-storybook"><a class="header" href="#welcome-to-storybook">Welcome to Storybook</a></h1>
<blockquote>
<p><strong>Bring characters to life with code that reads like stories.</strong></p>
</blockquote>
<p>Welcome! This tutorial will guide you through the Storybook language step by step. By the end, you will be able to create rich characters, define complex behaviors, build relationships, and model entire narrative worlds.</p>
<h2 id="what-you-will-learn"><a class="header" href="#what-you-will-learn">What You Will Learn</a></h2>
<p>In this tutorial, we follow Martha and her bakery family, using their daily lives to learn each concept:</p>
<ol>
<li><strong>Creating Characters</strong> - Define Martha with traits and descriptions</li>
<li><strong>Your First Behavior Tree</strong> - Give characters decision-making abilities</li>
<li><strong>Making Characters Act</strong> - Actions, conditions, and decorators</li>
<li><strong>Advanced Behaviors</strong> - Subtrees, parameters, and complex patterns</li>
<li><strong>Character Relationships</strong> - Model how characters interact</li>
<li><strong>Schedules and Time</strong> - Give characters daily routines</li>
<li><strong>Life Arcs</strong> - Track character development over time</li>
</ol>
<h2 id="what-is-storybook"><a class="header" href="#what-is-storybook">What is Storybook?</a></h2>
<p>Storybook is a domain-specific language (DSL) for narrative simulation. It lets you describe:</p>
<ul>
<li><strong>Who</strong> characters are (traits, backstory, species)</li>
<li><strong>What</strong> they do (behavior trees with decision logic)</li>
<li><strong>How</strong> they relate to others (relationships with perspectives)</li>
<li><strong>When</strong> they act (schedules and time-based routines)</li>
<li><strong>How they change</strong> (life arcs and state machines)</li>
</ul>
<p>All of this in syntax designed to be readable and expressive.</p>
<h2 id="your-first-storybook-file"><a class="header" href="#your-first-storybook-file">Your First Storybook File</a></h2>
<p>Create a file called <code>hello.sb</code> and add this:</p>
<pre><code class="language-storybook">character Martha {
age: 34
skill_level: 0.95
---description
A master baker who runs the most popular bakery in town,
known for her sourdough bread and apple pastries.
---
}
</code></pre>
<p>That is it. You have defined a character with two numeric fields and a prose description block. Let us break it down:</p>
<ul>
<li><code>character Martha</code> declares a new character named Martha</li>
<li><code>{ ... }</code> contains her attributes</li>
<li><code>age: 34</code> is an integer field</li>
<li><code>skill_level: 0.95</code> is a floating-point field (0.0 to 1.0)</li>
<li><code>---description ... ---</code> is a prose block for narrative text</li>
</ul>
<h2 id="key-concepts"><a class="header" href="#key-concepts">Key Concepts</a></h2>
<h3 id="everything-is-a-declaration"><a class="header" href="#everything-is-a-declaration">Everything is a Declaration</a></h3>
<p>Storybook files contain <strong>declarations</strong> named definitions of things in your world:</p>
<pre><code class="language-storybook">character Martha { ... } // A person or creature
behavior BakeRoutine { ... } // Decision-making logic
relationship Family { ... } // A connection between entities
schedule DailyRoutine { ... } // Time-based activities
life_arc Career { ... } // How someone changes over time
</code></pre>
<h3 id="fields-hold-data"><a class="header" href="#fields-hold-data">Fields Hold Data</a></h3>
<p>Fields use a simple <code>name: value</code> format:</p>
<pre><code class="language-storybook">age: 34 // Integer
skill_level: 0.95 // Float
name: "Martha Baker" // String
is_open: true // Boolean
wake_time: 04:30 // Time
bake_duration: 45m // Duration
</code></pre>
<h3 id="prose-blocks-tell-stories"><a class="header" href="#prose-blocks-tell-stories">Prose Blocks Tell Stories</a></h3>
<p>Prose blocks embed narrative text directly in your definitions:</p>
<pre><code class="language-storybook">---backstory
Martha learned to bake from her grandmother, starting at age
twelve with simple bread recipes. Over the years she mastered
sourdough, pastries, and specialty cakes, eventually opening
her own bakery.
---
</code></pre>
<p>You can have multiple prose blocks with different tags (<code>backstory</code>, <code>appearance</code>, <code>personality</code>, etc.) in a single declaration.</p>
<h2 id="project-structure"><a class="header" href="#project-structure">Project Structure</a></h2>
<p>A typical Storybook project organizes files into directories:</p>
<pre><code>my-world/
schema/
core_enums.sb // Enum definitions (skill levels, moods, etc.)
templates.sb // Reusable trait sets
beings.sb // Species definitions
world/
characters/
martha.sb // Character definitions
jane.sb
behaviors/
baking.sb // Behavior trees
relationships/
family.sb // Relationship definitions
</code></pre>
<p>Files reference each other using <code>use</code> statements:</p>
<pre><code class="language-storybook">use schema::core_enums::SkillLevel;
use schema::beings::Human;
character Martha: Human {
skill_level: expert
}
</code></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Ready to create your first character? Head to <a href="./02-creating-characters.html">Creating Characters</a> to start building Martha in detail.</p>
<hr />
<p><strong>Tip</strong>: You do not need to memorize everything now. This tutorial builds concepts gradually, and you can always refer back to the <a href="../reference/09-overview.html">Reference Guide</a> for precise syntax details.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../index.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/02-creating-characters.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="../index.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/02-creating-characters.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>

View File

@@ -0,0 +1,477 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Creating Characters - 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="creating-characters"><a class="header" href="#creating-characters">Creating Characters</a></h1>
<p>Characters are the heart of every Storybook world. In this chapter, you will learn how to define characters with fields, prose blocks, species, and templates.</p>
<h2 id="a-simple-character"><a class="header" href="#a-simple-character">A Simple Character</a></h2>
<p>The simplest character has a name and some fields:</p>
<pre><code class="language-storybook">character Martha {
age: 34
skill_level: 0.95
is_open: true
}
</code></pre>
<p>Fields use the <code>name: value</code> format. Storybook supports several value types:</p>
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Example</th><th>Description</th></tr></thead><tbody>
<tr><td>Integer</td><td><code>42</code></td><td>Whole numbers</td></tr>
<tr><td>Float</td><td><code>0.85</code></td><td>Decimal numbers</td></tr>
<tr><td>String</td><td><code>"hello"</code></td><td>Text in double quotes</td></tr>
<tr><td>Boolean</td><td><code>true</code> / <code>false</code></td><td>Yes or no values</td></tr>
<tr><td>Time</td><td><code>14:30</code></td><td>Clock times</td></tr>
<tr><td>Duration</td><td><code>2h30m</code></td><td>Time intervals</td></tr>
<tr><td>List</td><td><code>[1, 2, 3]</code></td><td>Ordered collections</td></tr>
</tbody></table>
</div>
<h2 id="adding-descriptions-with-prose-blocks"><a class="header" href="#adding-descriptions-with-prose-blocks">Adding Descriptions with Prose Blocks</a></h2>
<p>Prose blocks embed narrative text directly alongside data. They start and end with <code>---</code> and have a tag name:</p>
<pre><code class="language-storybook">character Martha {
age: 34
skill_level: 0.95
---backstory
Martha learned to bake from her grandmother, starting at age
twelve with simple bread recipes. She mastered sourdough and
pastries, eventually opening the most popular bakery in town.
---
---appearance
A confident woman in her mid-thirties, usually dusted with
flour. Her hands are calloused from years of kneading dough.
---
}
</code></pre>
<p>You can use any tag name you like. Common ones include <code>backstory</code>, <code>appearance</code>, <code>personality</code>, <code>motivation</code>, and <code>secrets</code>.</p>
<h2 id="defining-species"><a class="header" href="#defining-species">Defining Species</a></h2>
<p>Species define what a character fundamentally <em>is</em>. Define them separately, then reference them with the <code>:</code> syntax:</p>
<pre><code class="language-storybook">species Human {
lifespan: 70
---description
Bipedal mammals with complex language and tool use.
---
}
species Cat {
lifespan: 15
---description
Domestic cats make loyal companions and effective
pest control for bakeries.
---
}
</code></pre>
<p>Now use species when creating characters:</p>
<pre><code class="language-storybook">character Martha: Human {
age: 34
}
character Whiskers: Cat {
friendly: true
catches_mice: true
}
</code></pre>
<p>The <code>: Human</code> part says “Martha is a Human.” She inherits the species fields (like <code>lifespan: 70</code>) automatically.</p>
<p>A character can have only one species you cannot be both Human and Cat.</p>
<h3 id="but-what-about-hybrids"><a class="header" href="#but-what-about-hybrids">But what about hybrids?</a></h3>
<p>If you want a character that combines traits from different sources, use <strong>composition with templates</strong> instead:</p>
<pre><code class="language-storybook">species Human {
lifespan: 70
reasoning_ability: 1.0
}
template CulinaryExpert {
palate_sensitivity: 0.5..1.0
recipes_mastered: 0..500
can_identify_ingredients: true
}
template BusinessOwner {
business_acumen: 0.0..1.0
manages_finances: true
leadership: 0.0..1.0
}
// A character combining species with multiple templates
character Martha: Human from CulinaryExpert, BusinessOwner {
age: 34
// From CulinaryExpert
palate_sensitivity: 0.9
recipes_mastered: 150
can_identify_ingredients: true
// From BusinessOwner
business_acumen: 0.8
manages_finances: true
leadership: 0.85
// Unique traits
specialty: "sourdough"
years_experience: 22
---personality
A perfectionist in the kitchen who demands the best from her
ingredients and her team. Warm with customers but exacting
with her apprentices.
---
}
</code></pre>
<p>By combining a species with templates, you can achieve any combination you need. The species defines <em>what</em> the character fundamentally is, while templates add <em>traits</em> from other sources.</p>
<h2 id="reusing-traits-with-templates"><a class="header" href="#reusing-traits-with-templates">Reusing Traits with Templates</a></h2>
<p>Templates define reusable sets of attributes. Characters inherit from them using the <code>from</code> keyword:</p>
<pre><code class="language-storybook">template SkilledWorker {
skill_level: 0.0..1.0
years_experience: 0..50
}
template Baker {
include SkilledWorker
specialty: "general"
recipes_mastered: 0..500
}
</code></pre>
<p>Notice the <code>0.0..1.0</code> syntax that is a <strong>range</strong>. When a character uses this template, a specific value within that range is selected. Ranges are only valid in templates.</p>
<p>Characters can inherit from multiple templates:</p>
<pre><code class="language-storybook">character Martha: Human from Baker, BusinessOwner {
age: 34
skill_level: 0.95
specialty: "sourdough"
recipes_mastered: 150
}
</code></pre>
<p>The <code>from Baker, BusinessOwner</code> part says “Martha has the traits from both templates.” You can override any inherited field by specifying it directly.</p>
<h2 id="species-vs-templates"><a class="header" href="#species-vs-templates">Species vs. Templates</a></h2>
<p>Understanding the difference is important:</p>
<div class="table-wrapper"><table><thead><tr><th></th><th>Species (<code>:</code>)</th><th>Templates (<code>from</code>)</th></tr></thead><tbody>
<tr><td><strong>Meaning</strong></td><td>What the character <em>is</em></td><td>What the character <em>has</em></td></tr>
<tr><td><strong>Count</strong></td><td>Exactly one</td><td>Zero or more</td></tr>
<tr><td><strong>Example</strong></td><td><code>: Human</code></td><td><code>from Baker, BusinessOwner</code></td></tr>
</tbody></table>
</div>
<p>Think of it this way: a character <strong>is</strong> a Human, but <strong>has</strong> baking skills and business acumen.</p>
<h2 id="field-resolution"><a class="header" href="#field-resolution">Field Resolution</a></h2>
<p>When a character inherits from multiple sources, fields are resolved in priority order:</p>
<ol>
<li><strong>Species fields</strong> (lowest priority)</li>
<li><strong>Template fields</strong> (left to right in the <code>from</code> clause)</li>
<li><strong>Character fields</strong> (highest priority always wins)</li>
</ol>
<pre><code class="language-storybook">species Human {
speed: 1.0
}
template Warrior {
speed: 1.5
strength: 10
}
template Berserker {
speed: 2.0
strength: 15
}
character Conan: Human from Warrior, Berserker {
strength: 20
}
// Resolved: speed = 2.0 (Berserker), strength = 20 (Conan)
</code></pre>
<h2 id="using-enums-for-controlled-values"><a class="header" href="#using-enums-for-controlled-values">Using Enums for Controlled Values</a></h2>
<p>Enums define a fixed set of named values. They prevent typos and ensure consistency:</p>
<pre><code class="language-storybook">enum SkillLevel {
Novice,
Beginner,
Intermediate,
Advanced,
Expert,
Master
}
enum Specialty {
Sourdough,
Pastries,
Cakes,
Bread,
Confections
}
</code></pre>
<p>Use enum values as field values:</p>
<pre><code class="language-storybook">character Martha: Human {
skill_level: Master
specialty: Sourdough
}
</code></pre>
<p>If you write <code>skill_level: Professional</code>, the compiler will catch the mistake because <code>Professional</code> is not defined in the <code>SkillLevel</code> enum.</p>
<h2 id="importing-across-files"><a class="header" href="#importing-across-files">Importing Across Files</a></h2>
<p>Real projects split definitions across multiple files. Use the <code>use</code> statement to import:</p>
<pre><code class="language-storybook">// In world/characters/martha.sb
use schema::core_enums::{SkillLevel, Specialty};
use schema::templates::Baker;
use schema::beings::Human;
character Martha: Human from Baker {
skill_level: Master
specialty: Sourdough
}
</code></pre>
<p>The <code>use schema::core_enums::{SkillLevel, Specialty}</code> line imports two enums from the <code>schema/core_enums.sb</code> file. You can also import everything with <code>use schema::core_enums::*</code>.</p>
<h2 id="putting-it-all-together"><a class="header" href="#putting-it-all-together">Putting It All Together</a></h2>
<p>Here is a complete character definition with all the features:</p>
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Specialty};
use schema::templates::{Baker, BusinessOwner};
use schema::beings::Human;
character Martha: Human from Baker, BusinessOwner {
// Core identity
age: 34
skill_level: Master
specialty: Sourdough
// Professional
years_experience: 22
recipes_mastered: 150
can_teach: true
// Business
business_acumen: 0.8
leadership: 0.85
---backstory
Martha learned to bake from her grandmother, starting at age
twelve with simple bread recipes. She mastered sourdough and
pastries, eventually opening the most popular bakery in town.
---
---personality
A perfectionist in the kitchen who demands the best from her
ingredients and her team. Warm with customers but exacting
with her apprentices. Known for arriving at 4 AM to start
the morning batch.
---
}
</code></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Now that Martha exists, let us give her something to do. In <a href="./03-first-behavior-tree.html">Your First Behavior Tree</a>, you will learn how characters make decisions.</p>
<hr />
<p><strong>Reference</strong>: For complete character syntax details, see the <a href="../reference/10-characters.html">Characters Reference</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/01-welcome.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/03-first-behavior-tree.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/01-welcome.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/03-first-behavior-tree.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>

View File

@@ -0,0 +1,381 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Your First Behavior Tree - 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="your-first-behavior-tree"><a class="header" href="#your-first-behavior-tree">Your First Behavior Tree</a></h1>
<p>Behavior trees define how characters make decisions. They model the thought process: “Try this first, and if it fails, try that instead.” In this chapter, you will create your first behavior tree for Martha.</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 hierarchy of nodes that executes from top to bottom. Each node either <strong>succeeds</strong> or <strong>fails</strong>, and the tree uses that result to decide what to do next.</p>
<p>There are two fundamental building blocks:</p>
<ul>
<li><strong><code>choose</code></strong> (Selector): Try children in order until one succeeds. Think “try A, else try B, else try C.”</li>
<li><strong><code>then</code></strong> (Sequence): Run children in order, stopping if any fails. Think “do A, then B, then C all must succeed.”</li>
</ul>
<h2 id="your-first-tree"><a class="header" href="#your-first-tree">Your First Tree</a></h2>
<p>Let us give Martha a simple baking behavior:</p>
<pre><code class="language-storybook">behavior Martha_BakeRoutine {
choose what_to_do {
then fill_special_orders {
CheckSpecialOrders
PrepareSpecialIngredients
BakeSpecialItem
}
then daily_bread {
MixDough
KneadDough
BakeLoaves
}
CleanWorkstation
}
}
</code></pre>
<p>Reading this as a story:</p>
<blockquote>
<p>Martha will <strong>choose</strong> what to do. First, she tries to <strong>fill special orders</strong>: she checks for orders, prepares special ingredients, and bakes the item. If that path fails (maybe there are no special orders), she tries <strong>daily bread</strong>: she mixes dough, kneads it, and bakes loaves. If even that fails, she simply cleans her workstation.</p>
</blockquote>
<h2 id="understanding-choose-selector"><a class="header" href="#understanding-choose-selector">Understanding choose (Selector)</a></h2>
<p>A <code>choose</code> node tries its children one at a time. As soon as one succeeds, it stops and returns success. If all children fail, it returns failure.</p>
<pre><code class="language-storybook">choose response {
HandleUrgentOrder // Try first: handle urgent order
ServeCustomer // If that fails: serve a customer
RestockShelves // If that fails: restock
}
</code></pre>
<p>This is like a priority list the first successful option wins.</p>
<h2 id="understanding-then-sequence"><a class="header" href="#understanding-then-sequence">Understanding then (Sequence)</a></h2>
<p>A <code>then</code> node runs its children in order. If any child fails, the whole sequence fails and stops. All children must succeed for the sequence to succeed.</p>
<pre><code class="language-storybook">then make_sourdough {
MixDough // Must succeed
KneadDough // Must succeed
FirstRise // Must succeed
ShapeLoaves // Must succeed
}
</code></pre>
<p>If <code>MixDough</code> fails (no flour available), the whole process stops.</p>
<h2 id="naming-your-nodes"><a class="header" href="#naming-your-nodes">Naming Your Nodes</a></h2>
<p>Both <code>choose</code> and <code>then</code> accept optional labels:</p>
<pre><code class="language-storybook">choose daily_priority {
then morning_baking { ... }
then afternoon_sales { ... }
}
</code></pre>
<p>Labels are optional but highly recommended. They make your trees readable as narratives and help with debugging. Compare:</p>
<pre><code class="language-storybook">// Without labels (hard to read)
choose {
then { MixDough, BakeLoaves }
then { ServeCustomer, CollectPayment }
}
// With labels (reads like a story)
choose priority {
then baking { MixDough, BakeLoaves }
then sales { ServeCustomer, CollectPayment }
}
</code></pre>
<h2 id="combining-choose-and-then"><a class="header" href="#combining-choose-and-then">Combining choose and then</a></h2>
<p>Behavior trees become powerful when you nest selectors and sequences:</p>
<pre><code class="language-storybook">behavior Jane_PastryRoutine {
choose pastry_priorities {
// Highest priority: fill custom cake orders
then custom_orders {
ReviewCakeOrder
DesignDecoration
BakeAndDecorate
PackageForPickup
}
// If no orders: prepare display pastries
then display_pastries {
RollPastryDough
PrepareFillings
AssemblePastries
ArrangeDisplay
}
// Default: experiment with new recipes
ExperimentWithFlavors
}
}
</code></pre>
<p>Reading this as narrative:</p>
<blockquote>
<p>Jane always prioritizes custom cake orders. She reviews the order, designs the decoration, bakes and decorates, then packages it. If there are no orders, she prepares display pastries. If there is nothing else to do, she experiments with new flavors.</p>
</blockquote>
<h2 id="actions"><a class="header" href="#actions">Actions</a></h2>
<p>The leaf nodes in a behavior tree are <strong>actions</strong> concrete things a character does:</p>
<pre><code class="language-storybook">MixDough // Simple action
KneadDough // Simple action
ServeCustomer // Simple action
</code></pre>
<p>Actions are identifiers that the runtime interprets. They represent the actual behaviors executed in your simulation.</p>
<h2 id="a-complete-example"><a class="header" href="#a-complete-example">A Complete Example</a></h2>
<p>Here is a behavior tree for the morning rush at the bakery:</p>
<pre><code class="language-storybook">behavior Bakery_MorningRush {
---description
Handles the busy morning rush when customers are
lining up for fresh bread and pastries.
---
choose morning_priority {
then serve_waiting_customer {
GreetCustomer
TakeOrder
PackageItems
CollectPayment
ThankCustomer
}
then restock_display {
CheckDisplayLevels
FetchFromKitchen
ArrangeOnShelves
}
then quick_bake {
CheckInventory
StartQuickBatch
MonitorOven
}
}
}
</code></pre>
<p>Notice the prose block (<code>---description ... ---</code>) at the top of the behavior. You can document what the behavior does right alongside the code.</p>
<h2 id="behavior-character-connection"><a class="header" href="#behavior-character-connection">Behavior-Character Connection</a></h2>
<p>Characters link to behaviors using the <code>uses behaviors</code> clause:</p>
<pre><code class="language-storybook">character Martha: Human {
age: 34
uses behaviors: [
{ tree: Martha_BakeRoutine },
{ tree: HandleEmergency }
]
}
</code></pre>
<p>This tells the simulation that Martha uses two behavior trees. We will cover advanced behavior linking (priorities, conditions) in <a href="./04-making-characters-act.html">Making Characters Act</a>.</p>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Your behavior trees so far make decisions between options and run sequences of actions. In <a href="./04-making-characters-act.html">Making Characters Act</a>, you will learn how to add conditions, decorators, and parameters to create truly dynamic behaviors.</p>
<hr />
<p><strong>Reference</strong>: For complete behavior tree syntax, see the <a href="../reference/11-behavior-trees.html">Behavior Trees Reference</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/02-creating-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="../tutorial/04-making-characters-act.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/02-creating-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="../tutorial/04-making-characters-act.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>

View File

@@ -0,0 +1,453 @@
<!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>

View File

@@ -0,0 +1,417 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Advanced Behaviors - 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="advanced-behaviors"><a class="header" href="#advanced-behaviors">Advanced Behaviors</a></h1>
<p>You have learned the fundamentals of behavior trees. This chapter covers advanced patterns: complex decision hierarchies, modular design with subtrees, and state-driven behavior.</p>
<h2 id="deep-decision-trees"><a class="header" href="#deep-decision-trees">Deep Decision Trees</a></h2>
<p>Real characters need layered decision-making. Nest selectors and sequences to create rich AI:</p>
<pre><code class="language-storybook">behavior Baker_DailyAI {
choose daily_activity {
// Morning: Prepare the bakery
then morning_prep {
if(time_is_morning)
then prep_sequence {
LightOven
PrepareDough
StartFirstBatch
}
}
// Day: Serve customers
then day_service {
if(time_is_daytime)
choose service_mode {
then serve_customer {
if(customer_waiting)
GreetCustomer
TakeOrder
PackageItems
CollectPayment
}
then restock {
if(display_low)
FetchFromKitchen
}
CleanCounter
}
}
// Evening: Close up
then evening_close {
if(time_is_evening)
then close_sequence {
TurnOffOvens
CleanKitchen
CountRegister
LockUp
}
}
}
}
</code></pre>
<p>Each level of nesting refines the decision. The outer <code>choose</code> selects the time of day; inner nodes handle the specifics.</p>
<h2 id="modular-subtrees"><a class="header" href="#modular-subtrees">Modular Subtrees</a></h2>
<p>Large behavior trees become unwieldy. Break them into focused subtrees and compose with <code>include</code>:</p>
<pre><code class="language-storybook">// Focused subtree: just baking
behavior Baking_Sourdough {
then sourdough_sequence {
MixDough
KneadDough
FirstRise
ShapeLoaves
}
}
// Focused subtree: just customer service
behavior Service_ServeCustomer {
then service_sequence {
GreetCustomer
TakeOrder
PackageItems
CollectPayment
}
}
// Composition: combine subtrees
behavior Martha_FullDay {
choose activity {
then morning_baking {
if(time_is_morning)
include Baking_Sourdough
include Baking_Pastries
}
then afternoon_sales {
if(time_is_afternoon)
include Service_ServeCustomer
}
CleanWorkstation
}
}
</code></pre>
<p>Benefits of modular subtrees:</p>
<ul>
<li>Each subtree is testable in isolation</li>
<li>Multiple characters can share subtrees</li>
<li>Changes propagate automatically</li>
</ul>
<h2 id="conditional-behavior-selection"><a class="header" href="#conditional-behavior-selection">Conditional Behavior Selection</a></h2>
<p>Use conditions to switch between behavioral modes:</p>
<pre><code class="language-storybook">behavior SmartBaker {
choose strategy {
// Busy mode when there are many customers
then busy_mode {
if(customer_count &gt; 5 and inventory_sufficient)
choose rush_tactics {
ServeFastOrder
QuickRestock
ExpressBake
}
}
// Careful mode when supplies are low
then careful_mode {
if(inventory_low or special_ingredients_missing)
choose conservation_tactics {
ReducePortions
SubstituteIngredients
OrderEmergencySupply
}
}
// Normal mode otherwise
then normal_mode {
if(customer_count &lt;= 5 and inventory_sufficient)
StandardRoutine
}
}
}
</code></pre>
<h2 id="decorator-combinations"><a class="header" href="#decorator-combinations">Decorator Combinations</a></h2>
<p>Combine decorators to build sophisticated control patterns:</p>
<pre><code class="language-storybook">behavior Baker_SpecialRecipe {
// Only when inventory is sufficient
if(has_special_ingredients) {
// Limited to once per hour
cooldown(1h) {
// Must complete within 30 minutes
timeout(30m) {
// Try up to 3 times
retry(3) {
then bake_special {
PrepareSpecialDough
BakeAtPreciseTemperature
}
}
}
}
}
}
</code></pre>
<h2 id="prose-documentation"><a class="header" href="#prose-documentation">Prose Documentation</a></h2>
<p>Add narrative context to complex behaviors with prose blocks:</p>
<pre><code class="language-storybook">behavior Elena_TrainingSession {
---description
Elena practicing a new recipe under Martha's guidance.
Uses retry decorator for persistence and if for
checking readiness.
---
choose training_strategy {
then practice_supervised {
if(martha_available)
retry(3) {
then attempt_recipe {
ReviewRecipeSteps
MeasureIngredients
MixAndKnead
CheckWithMartha
}
}
}
then practice_solo {
if(not martha_available)
then solo_attempt {
ReviewRecipeNotes
AttemptRecipeFromMemory
TasteTestResult
}
}
}
}
</code></pre>
<h2 id="design-tips"><a class="header" href="#design-tips">Design Tips</a></h2>
<p><strong>Prefer shallow trees</strong>: Break deep nesting into subtrees with <code>include</code>.</p>
<p><strong>Name every composite node</strong>: Labels make trees self-documenting.</p>
<p><strong>Use decorators for control flow</strong>: Timing, repetition, and gating belong in decorators, not in action logic.</p>
<p><strong>Keep actions atomic</strong>: Each action should do one thing. Complex operations are sequences of simple actions.</p>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Characters do not exist in isolation. In <a href="./06-relationships.html">Character Relationships</a>, you will model how characters connect to each other friendships, rivalries, parent-child bonds, and more.</p>
<hr />
<p><strong>Reference</strong>: See <a href="../reference/11-behavior-trees.html">Behavior Trees Reference</a> and <a href="../reference/12-decorators.html">Decorators Reference</a> for complete syntax.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/04-making-characters-act.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/06-relationships.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/04-making-characters-act.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/06-relationships.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>

View File

@@ -0,0 +1,399 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Character Relationships - 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="character-relationships"><a class="header" href="#character-relationships">Character Relationships</a></h1>
<p>Characters exist in a web of connections friendships, rivalries, parent-child bonds, and complex power dynamics. In Storybook, relationships are first-class declarations that capture these connections with nuance and perspective.</p>
<h2 id="basic-relationships"><a class="header" href="#basic-relationships">Basic Relationships</a></h2>
<p>The simplest relationship connects two characters with shared fields:</p>
<pre><code class="language-storybook">relationship MarthaAndEmma {
Martha as parent {}
Emma as child {}
bond: 0.95
type: "parent_child"
}
</code></pre>
<p>This says Martha and Emma share a relationship with a bond strength of 0.95 (very close). The <code>bond</code> field is shared it applies equally to both participants.</p>
<h2 id="adding-roles"><a class="header" href="#adding-roles">Adding Roles</a></h2>
<p>Roles label each participants function in the relationship:</p>
<pre><code class="language-storybook">relationship ParentChild {
Martha as parent
Emma as child
bond: 0.95
guardianship: true
}
</code></pre>
<p>The <code>as parent</code> and <code>as child</code> labels clarify who plays which role. Roles are descriptive you can use any name that makes sense.</p>
<h2 id="perspectives-self-and-other"><a class="header" href="#perspectives-self-and-other">Perspectives: Self and Other</a></h2>
<p>Real relationships are not symmetric. How one person sees the relationship may differ from how the other sees it. Storybook handles this with <code>self</code> and <code>other</code> blocks:</p>
<pre><code class="language-storybook">relationship MentorApprentice {
Martha as mentor self {
patience: 0.8
investment_in_student: 0.9
} other {
sees_potential: 0.85
frustration_level: 0.2
}
Elena as apprentice self {
dedication: 0.9
overwhelmed: 0.4
} other {
admiration: 0.95
desire_to_impress: 0.9
}
bond: 0.85
}
</code></pre>
<p>Reading this:</p>
<ul>
<li><strong>Marthas self view</strong>: She feels patient (80%), highly invested in her student</li>
<li><strong>Marthas view of Elena (other)</strong>: Sees high potential (85%) with low frustration (20%)</li>
<li><strong>Elenas self view</strong>: Dedicated (90%) but sometimes overwhelmed (40%)</li>
<li><strong>Elenas view of Martha (other)</strong>: Deep admiration (95%), strong desire to impress (90%)</li>
<li><strong>Shared</strong>: Their bond strength is 0.85</li>
</ul>
<h2 id="prose-in-relationships"><a class="header" href="#prose-in-relationships">Prose in Relationships</a></h2>
<p>Relationships can include narrative descriptions for each participant:</p>
<pre><code class="language-storybook">relationship MarthaAndGregory {
Martha {
role: shopkeeper
values_loyalty: 0.9
---perspective
Martha appreciates Gregory's unwavering loyalty. He has
been buying her sourdough loaf every morning for fifteen
years. Their brief daily exchanges about the weather and
local gossip are a comforting routine.
---
}
Gregory {
role: regular_customer
always_orders: "sourdough_loaf"
---perspective
Gregory considers Martha's bakery a cornerstone of his
daily routine. The bread is excellent, but it is the brief
human connection that keeps him coming back. He worries
about what would happen if she ever retired.
---
}
bond: 0.7
}
</code></pre>
<h2 id="multi-party-relationships"><a class="header" href="#multi-party-relationships">Multi-Party Relationships</a></h2>
<p>Relationships can involve more than two participants:</p>
<pre><code class="language-storybook">relationship BakerFamily {
Martha as parent
Jane as parent
Emma as child
household: "Baker Residence"
family_bond: 0.95
dinner_time: 18:00
---dynamics
A loving family running a bakery together. Martha handles
the bread, Jane manages pastries, and Emma helps out on
weekends while learning the craft.
---
}
</code></pre>
<h2 id="asymmetric-awareness"><a class="header" href="#asymmetric-awareness">Asymmetric Awareness</a></h2>
<p>Relationships can model situations where one party does not know the relationship exists:</p>
<pre><code class="language-storybook">relationship BossAndNewHire {
Martha {
role: boss
aware_of_struggles: false
expects: high_quality_work
---perspective
Martha sees the new hire as competent and expects them
to learn the bakery routines quickly. She has no idea
they are struggling with the early morning schedule.
---
}
NewHire {
role: employee
intimidated: 0.8
hides_struggles: true
---perspective
The new hire is in awe of Martha's skill but terrified
of disappointing her. They arrive thirty minutes early
every day to practice techniques before she gets in.
---
}
bond: 0.4
}
</code></pre>
<h2 id="institutional-relationships"><a class="header" href="#institutional-relationships">Institutional Relationships</a></h2>
<p>Institutions can participate in relationships too:</p>
<pre><code class="language-storybook">relationship GuildMembership {
Martha as member
BakersGuild as organization
membership_since: "2015-01-01"
standing: "good"
dues_paid: true
}
</code></pre>
<h2 id="building-a-relationship-web"><a class="header" href="#building-a-relationship-web">Building a Relationship Web</a></h2>
<p>Multiple relationships create a rich social network:</p>
<pre><code class="language-storybook">relationship Marriage {
Martha as spouse
Jane as spouse
bond: 0.9
}
relationship MentorApprentice {
Martha as mentor
Elena as apprentice
bond: 0.85
}
relationship RegularCustomer {
Martha as shopkeeper
Gregory as customer
bond: 0.7
}
relationship Colleagues {
Martha as peer
NeighborBaker as peer
bond: 0.5
competitive: true
}
</code></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Characters have traits, behaviors, and relationships. In <a href="./07-schedules.html">Schedules and Time</a>, you will give them daily routines and time-based activities.</p>
<hr />
<p><strong>Reference</strong>: For complete relationship syntax, see the <a href="../reference/15-relationships.html">Relationships Reference</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/05-advanced-behaviors.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/07-schedules.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/05-advanced-behaviors.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/07-schedules.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>

View File

@@ -0,0 +1,422 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Schedules and Time - 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="schedules-and-time"><a class="header" href="#schedules-and-time">Schedules and Time</a></h1>
<p>Characters live in time. A baker wakes before dawn to prepare dough; a guard patrols during the day shift; an innkeeper serves customers until late. Schedules define these time-based routines.</p>
<h2 id="basic-schedules"><a class="header" href="#basic-schedules">Basic Schedules</a></h2>
<p>A schedule contains time blocks, each with a time range and an action:</p>
<pre><code class="language-storybook">schedule SimpleBaker {
block morning_prep {
05:00 - 08:00
action: baking::prepare_dough
}
block sales {
08:00 - 14:00
action: baking::serve_customers
}
block cleanup {
14:00 - 15:00
action: baking::close_shop
}
}
</code></pre>
<p>Time ranges use 24-hour clock format (<code>HH:MM</code>). The <code>action</code> field links to a behavior tree that drives the characters activity during that block.</p>
<h2 id="named-blocks"><a class="header" href="#named-blocks">Named Blocks</a></h2>
<p>Blocks can have names (like <code>morning_prep</code> above). Named blocks are important for schedule composition they allow child schedules to override specific blocks by name.</p>
<h2 id="linking-schedules-to-characters"><a class="header" href="#linking-schedules-to-characters">Linking Schedules to Characters</a></h2>
<p>Characters use the <code>uses schedule</code> clause:</p>
<pre><code class="language-storybook">character Baker: Human {
uses schedule: SimpleBaker
}
</code></pre>
<p>For multiple schedules:</p>
<pre><code class="language-storybook">character Innkeeper: Human {
uses schedules: [WeekdaySchedule, WeekendSchedule]
}
</code></pre>
<h2 id="temporal-constraints"><a class="header" href="#temporal-constraints">Temporal Constraints</a></h2>
<p>Blocks can be restricted to specific times using temporal constraints:</p>
<h3 id="season-constraints"><a class="header" href="#season-constraints">Season Constraints</a></h3>
<pre><code class="language-storybook">schedule SeasonalBaker {
block summer_hours {
06:00 - 20:00
action: baking::long_shift
on season summer
}
block winter_hours {
07:00 - 18:00
action: baking::short_shift
on season winter
}
}
</code></pre>
<h3 id="day-of-week-constraints"><a class="header" href="#day-of-week-constraints">Day of Week Constraints</a></h3>
<pre><code class="language-storybook">schedule WeeklyPattern {
block weekday_work {
09:00 - 17:00
action: work::standard
on day monday
}
block weekend_rest {
10:00 - 16:00
action: leisure::relax
on day saturday
}
}
</code></pre>
<p>Temporal constraint values (like <code>summer</code> or <code>monday</code>) reference enums defined in your storybook:</p>
<pre><code class="language-storybook">enum Season { spring, summer, fall, winter }
enum DayOfWeek { monday, tuesday, wednesday, thursday, friday, saturday, sunday }
</code></pre>
<h2 id="recurring-events"><a class="header" href="#recurring-events">Recurring Events</a></h2>
<p>Use <code>recurs</code> to define events that repeat on a schedule:</p>
<pre><code class="language-storybook">schedule MarketSchedule {
// Regular daily hours
block work {
08:00 - 17:00
action: shop::regular_sales
}
// Market day every Saturday
recurs MarketDay on day saturday {
block setup {
06:00 - 08:00
action: market::setup_stall
}
block busy_market {
08:00 - 18:00
action: market::busy_sales
}
block teardown {
18:00 - 20:00
action: market::pack_up
}
}
}
</code></pre>
<p>Recurrences take priority over regular blocks. On Saturdays, the <code>MarketDay</code> blocks replace the regular <code>work</code> block.</p>
<h2 id="schedule-composition-with-extends"><a class="header" href="#schedule-composition-with-extends">Schedule Composition with extends</a></h2>
<p>Schedules can extend other schedules, inheriting and overriding blocks:</p>
<pre><code class="language-storybook">schedule BaseShopkeeper {
block open {
09:00 - 17:00
action: shop::standard_hours
}
}
schedule EarlyBaker extends BaseShopkeeper {
block open {
05:00 - 13:00
action: baking::early_shift
}
}
</code></pre>
<p>The <code>EarlyBaker</code> schedule overrides the <code>open</code> block by name same block name, different hours. Any blocks not overridden are inherited unchanged.</p>
<p>You can chain extensions:</p>
<pre><code class="language-storybook">schedule MasterBaker extends EarlyBaker {
block open {
03:00 - 11:00
action: baking::master_work
}
block teaching {
14:00 - 16:00
action: baking::teach_apprentice
}
}
</code></pre>
<p><code>MasterBaker</code> overrides <code>open</code> again and adds a new <code>teaching</code> block.</p>
<h2 id="overnight-blocks"><a class="header" href="#overnight-blocks">Overnight Blocks</a></h2>
<p>Time ranges can span midnight:</p>
<pre><code class="language-storybook">schedule NightGuard {
block night_patrol {
22:00 - 06:00
action: security::patrol
}
}
</code></pre>
<p>The system interprets this as 22:00 to midnight on day one, then midnight to 06:00 on day two.</p>
<h2 id="a-complete-schedule-example"><a class="header" href="#a-complete-schedule-example">A Complete Schedule Example</a></h2>
<pre><code class="language-storybook">schedule MasterBaker_FullYear {
// Daily base
block prep {
04:00 - 06:00
action: baking::prepare
}
block baking {
06:00 - 10:00
action: baking::bake
}
block sales {
10:00 - 16:00
action: baking::serve
}
block cleanup {
16:00 - 17:00
action: baking::clean
}
// Summer extended hours
block summer_sales {
10:00 - 20:00
action: baking::busy_summer
on season summer
}
// Weekly market
recurs MarketDay on day saturday {
block market_prep {
02:00 - 04:00
action: baking::market_prep
}
block market_sales {
08:00 - 18:00
action: baking::market_rush
}
}
// Annual harvest festival
recurs HarvestFestival on dates "Sep 20" .. "Sep 25" {
block festival {
06:00 - 23:00
action: baking::festival_mode
}
}
}
</code></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Characters now have traits, behaviors, relationships, and schedules. In <a href="./08-life-arcs.html">Life Arcs</a>, you will learn how to model character development over time how they grow, change, and evolve through different phases of life.</p>
<hr />
<p><strong>Reference</strong>: For complete schedule syntax, see the <a href="../reference/14-schedules.html">Schedules Reference</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/06-relationships.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/08-life-arcs.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/06-relationships.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/08-life-arcs.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>

View File

@@ -0,0 +1,451 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Life Arcs - 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="life-arcs"><a class="header" href="#life-arcs">Life Arcs</a></h1>
<p>Characters grow and change. A timid apprentice becomes a confident master baker. A new employee finds their place in the team. Life arcs model these transformations as state machines discrete phases with conditions that trigger transitions between them.</p>
<h2 id="what-is-a-life-arc"><a class="header" href="#what-is-a-life-arc">What is a Life Arc?</a></h2>
<p>A life arc defines:</p>
<ul>
<li><strong>States</strong>: Distinct phases (e.g., “apprentice”, “journeyman”, “master”)</li>
<li><strong>Transitions</strong>: Conditions that move between states (e.g., “when skill &gt; 80, become master”)</li>
<li><strong>On-enter actions</strong>: Changes that happen when entering a state (e.g., set confidence to 0.9)</li>
</ul>
<h2 id="your-first-life-arc"><a class="header" href="#your-first-life-arc">Your First Life Arc</a></h2>
<pre><code class="language-storybook">life_arc BakerCareer {
state apprentice {
on enter {
Baker.skill_level: 0.2
Baker.confidence: 0.3
}
on skill_level &gt; 0.5 -&gt; journeyman
}
state journeyman {
on enter {
Baker.confidence: 0.6
}
on skill_level &gt; 0.8 -&gt; master
}
state master {
on enter {
Baker.confidence: 0.95
Baker.can_teach: true
}
}
}
</code></pre>
<p>Reading this as a story:</p>
<blockquote>
<p>The baker starts as an <strong>apprentice</strong> with low skill and confidence. When skill exceeds 0.5, they become a <strong>journeyman</strong> with improved confidence. When skill exceeds 0.8, they become a <strong>master</strong> who is confident and can teach others.</p>
</blockquote>
<h2 id="states"><a class="header" href="#states">States</a></h2>
<p>Each state represents a distinct phase. States contain:</p>
<ul>
<li><strong>on enter block</strong>: Field updates that happen when entering the state</li>
<li><strong>Transitions</strong>: Conditions that trigger moving to another state</li>
<li><strong>Prose blocks</strong>: Narrative descriptions</li>
</ul>
<pre><code class="language-storybook">state early_apprentice {
on enter {
Elena.skill_level: novice
Elena.confidence: timid
}
on recipes_mastered &gt; 5 -&gt; growing_apprentice
on quit_apprenticeship -&gt; former_apprentice
---narrative
Elena's hands shake as she measures flour. She checks the
recipe three times before adding each ingredient. Martha
patiently corrects her technique.
---
}
</code></pre>
<h2 id="transitions"><a class="header" href="#transitions">Transitions</a></h2>
<p>Transitions use expressions to decide when to change state:</p>
<pre><code class="language-storybook">// Simple conditions
on health &lt; 20 -&gt; fleeing
on quest_complete -&gt; celebrating
// Boolean fields
on is_hostile -&gt; combat
on not ready -&gt; waiting
// Equality checks
on status is active -&gt; active_state
on name is "Martha" -&gt; found_martha
// Complex conditions
on health &lt; 50 and enemy_count &gt; 3 -&gt; retreat
on (tired and hungry) or exhausted -&gt; resting
</code></pre>
<h3 id="transition-priority"><a class="header" href="#transition-priority">Transition Priority</a></h3>
<p>When multiple transitions could fire, the <strong>first one in declaration order</strong> wins:</p>
<pre><code class="language-storybook">state combat {
on health &lt; 10 -&gt; desperate // Checked first
on health &lt; 50 -&gt; defensive // Checked second
on surrounded -&gt; retreat // Checked third
}
</code></pre>
<p>If health is 5, only <code>desperate</code> triggers, even though <code>defensive</code> is also true.</p>
<h2 id="on-enter-actions"><a class="header" href="#on-enter-actions">On-Enter Actions</a></h2>
<p>The <code>on enter</code> block sets field values when entering a state. It runs exactly once per transition:</p>
<pre><code class="language-storybook">state master_baker {
on enter {
Elena.skill_level: master
Elena.confidence: commanding
Elena.can_teach: true
}
}
</code></pre>
<p>The syntax is <code>EntityName.field: value</code>. This updates the referenced characters field.</p>
<h2 id="elenas-complete-journey"><a class="header" href="#elenas-complete-journey">Elenas Complete Journey</a></h2>
<p>Here is a complete life arc tracking Elenas growth from apprentice to master:</p>
<pre><code class="language-storybook">life_arc ElenaCareer {
---description
Tracks Elena's professional and personal growth as she
progresses from nervous apprentice to confident master baker.
---
state early_apprentice {
on enter {
Elena.skill_level: novice
Elena.confidence: timid
}
on recipes_mastered &gt; 5 -&gt; growing_apprentice
---narrative
Elena's hands shake as she measures flour. She checks the
recipe three times before adding each ingredient. Martha
patiently corrects her technique.
---
}
state growing_apprentice {
on enter {
Elena.skill_level: beginner
Elena.confidence: uncertain
}
on recipes_mastered &gt; 15 -&gt; journeyman
---narrative
The shaking stops. Elena can make basic breads without
looking at the recipe. She still doubts herself but
Martha's encouragement is taking root.
---
}
state journeyman {
on enter {
Elena.skill_level: intermediate
Elena.confidence: growing
Elena.can_work_independently: true
}
on recipes_mastered &gt; 50 -&gt; master
---narrative
Elena runs the morning shift alone while Martha handles
special orders. Customers start asking for "Elena's rolls."
---
}
state master {
on enter {
Elena.skill_level: master
Elena.confidence: commanding
Elena.can_teach: true
}
---narrative
Master Baker Elena. She has earned it. The guild acknowledges
her mastery, and Martha beams with pride. Elena begins
mentoring her own apprentice.
---
}
}
</code></pre>
<h2 id="common-patterns"><a class="header" href="#common-patterns">Common Patterns</a></h2>
<h3 id="hub-and-spoke"><a class="header" href="#hub-and-spoke">Hub-and-Spoke</a></h3>
<p>A central idle state with branches to specialized states:</p>
<pre><code class="language-storybook">life_arc BakerBehavior {
state idle {
on customer_arrived -&gt; serving
on order_placed -&gt; baking
on delivery_arrived -&gt; receiving
}
state serving { on customer_served -&gt; idle }
state baking { on batch_complete -&gt; idle }
state receiving { on delivery_processed -&gt; idle }
}
</code></pre>
<h3 id="linear-progression"><a class="header" href="#linear-progression">Linear Progression</a></h3>
<p>States form a one-way sequence (quests, tutorials):</p>
<pre><code class="language-storybook">life_arc Tutorial {
state intro { on clicked_start -&gt; movement }
state movement { on moved_forward -&gt; combat }
state combat { on defeated_enemy -&gt; inventory }
state inventory { on opened_inventory -&gt; complete }
state complete {}
}
</code></pre>
<h3 id="cyclic-states"><a class="header" href="#cyclic-states">Cyclic States</a></h3>
<p>States form a loop (day/night, seasons, mood swings):</p>
<pre><code class="language-storybook">life_arc DayNightCycle {
state dawn { on hour &gt;= 8 -&gt; day }
state day { on hour &gt;= 18 -&gt; dusk }
state dusk { on hour &gt;= 20 -&gt; night }
state night { on hour &gt;= 6 -&gt; dawn }
}
</code></pre>
<h2 id="tips"><a class="header" href="#tips">Tips</a></h2>
<p><strong>Order transitions by urgency</strong>: Put the most critical conditions first, since the first true transition wins.</p>
<p><strong>Use descriptive state names</strong>: <code>waiting_for_customer</code> is clearer than <code>state1</code>.</p>
<p><strong>Add narrative prose blocks</strong>: They make life arcs readable as stories and serve as documentation.</p>
<p><strong>Avoid orphan states</strong>: Every state should be reachable from some other state (the compiler will warn you about unreachable states).</p>
<h2 id="what-you-have-learned"><a class="header" href="#what-you-have-learned">What You Have Learned</a></h2>
<p>Congratulations! You have completed the tutorial. You now know how to:</p>
<ul>
<li>Create characters with species, templates, and enums</li>
<li>Build behavior trees with selectors, sequences, and decorators</li>
<li>Add conditions and action parameters</li>
<li>Model relationships with perspectives</li>
<li>Define time-based schedules</li>
<li>Track character development with life arcs</li>
</ul>
<h2 id="where-to-go-next"><a class="header" href="#where-to-go-next">Where to Go Next</a></h2>
<ul>
<li><strong><a href="../reference/09-overview.html">Reference Guide</a></strong>: Complete syntax specifications</li>
<li><strong><a href="../advanced/20-patterns.html">Design Patterns</a></strong>: Common patterns and best practices</li>
<li><strong><a href="../examples/24-baker-family-complete.html">Examples Gallery</a></strong>: Full working examples to learn from</li>
</ul>
<hr />
<p><strong>Reference</strong>: For complete life arc syntax, see the <a href="../reference/13-life-arcs.html">Life Arcs Reference</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/07-schedules.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/09-locations-institutions.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/07-schedules.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/09-locations-institutions.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>

View File

@@ -0,0 +1,635 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Locations and Institutions - 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="locations-and-institutions"><a class="header" href="#locations-and-institutions">Locations and Institutions</a></h1>
<p>So far you have created characters, given them behaviors, connected them with relationships, scheduled their days, and guided them through life arcs. But characters need places to <em>be</em> and organizations to <em>belong to</em>. That is what locations and institutions provide.</p>
<hr />
<h2 id="what-are-locations"><a class="header" href="#what-are-locations">What Are Locations?</a></h2>
<p>A <strong>location</strong> is a place in your world. It can be a building, a room, a park, a city any space where things happen. Locations hold fields that describe the place and optional prose blocks for narrative detail.</p>
<h3 id="your-first-location"><a class="header" href="#your-first-location">Your First Location</a></h3>
<p>Lets create the bakery where Martha works:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
address: "14 Main Street"
capacity: 30
}
</code></pre>
<p>That is all it takes. The <code>location</code> keyword, a name, and a block of fields. Every field is a key-value pair, and you choose whatever fields make sense for your world.</p>
<h3 id="adding-detail"><a class="header" href="#adding-detail">Adding Detail</a></h3>
<p>A real location needs more than three fields. Lets flesh out the bakery:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
address: "14 Main Street"
capacity: 30
employees: 4
specialty: "artisan sourdough"
daily_output_loaves: 80..120
open: true
established: "2011"
}
</code></pre>
<p>Notice <code>daily_output_loaves: 80..120</code> that is a range. Each simulation run can pick a different number of loaves, adding natural variation.</p>
<h3 id="prose-blocks"><a class="header" href="#prose-blocks">Prose Blocks</a></h3>
<p>Bare fields are good for data, but locations also need narrative flavor. Use prose blocks:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
address: "14 Main Street"
capacity: 30
---description
A warm, inviting bakery on Main Street. The smell of fresh bread
wafts out the door every morning at dawn. Martha has run the shop
for fifteen years, and the locals consider it the heart of the
neighborhood.
---
}
</code></pre>
<p>Prose blocks start with <code>---tag_name</code> and end with <code>---</code>. The tag name (<code>description</code> here) becomes the key. You can have as many prose blocks as you want:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
---description
The bakery on Main Street...
---
---history
Originally a hardware store, Martha converted the space in 2011...
---
---atmosphere
Flour dust catches the light from tall windows...
---
}
</code></pre>
<hr />
<h2 id="building-a-world-with-locations"><a class="header" href="#building-a-world-with-locations">Building a World with Locations</a></h2>
<p>Locations work best when they form a coherent world. Here is the Baker familys neighborhood:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
address: "14 Main Street"
capacity: 30
owner: Martha
---description
Martha's artisan bakery. The stone oven was imported from France.
---
}
location BakerHome {
type: residence
address: "22 Elm Lane"
bedrooms: 4
has_garden: true
---description
The Baker family home. Martha insisted on an oversized kitchen.
---
}
location BakersGuildHall {
type: guild_hall
address: "7 Guild Row"
capacity: 100
established: "1892"
---description
The historic headquarters of the Bakers Guild.
---
}
location TownSquare {
type: public_square
capacity: 500
has_fountain: true
has_market_stalls: true
---description
The central gathering place. On weekends, the farmers market
fills the square with produce stalls.
---
}
</code></pre>
<h3 id="modeling-hierarchy"><a class="header" href="#modeling-hierarchy">Modeling Hierarchy</a></h3>
<p>Storybook does not enforce a built-in parent-child relationship for locations. Instead, you use fields to express hierarchy:</p>
<pre><code class="language-storybook">location MainStreet {
type: street
district: TownCenter
shops: 12
}
location BakersBakery {
type: bakery
street: MainStreet
district: TownCenter
}
</code></pre>
<p>This convention-based approach keeps the language simple while letting you model whatever spatial relationships your world needs.</p>
<hr />
<h2 id="what-are-institutions"><a class="header" href="#what-are-institutions">What Are Institutions?</a></h2>
<p>An <strong>institution</strong> is an organization, group, or system. Think of it as a character that represents a collective: a guild, a government, a school, a business. Institutions have a key capability that locations lack they can <strong>use behaviors and schedules</strong>, just like characters.</p>
<h3 id="your-first-institution"><a class="header" href="#your-first-institution">Your First Institution</a></h3>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
members: 50
founded: "1892"
reputation: 0.85
}
</code></pre>
<p>This looks just like a location so far. The difference comes when you add behaviors.</p>
<h3 id="institutions-with-behaviors"><a class="header" href="#institutions-with-behaviors">Institutions with Behaviors</a></h3>
<p>Institutions can act. The <code>uses behaviors</code> clause links behavior trees to the institution:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
members: 50
reputation: 0.85
uses behaviors: [
{ tree: ManageApprentices },
{ tree: NegotiateSuppliers },
{ tree: HostEvents }
]
}
</code></pre>
<p>Each entry in the list is a behavior link object with a <code>tree</code> field. This tells the simulation engine that the Bakers Guild can manage apprentices, negotiate with suppliers, and host events.</p>
<h3 id="behavior-priorities"><a class="header" href="#behavior-priorities">Behavior Priorities</a></h3>
<p>Not all behaviors are equally important. Use the <code>priority</code> field:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
uses behaviors: [
{ tree: ManageApprentices, priority: normal },
{ tree: NegotiateSuppliers, priority: high },
{ tree: HostEvents, priority: low }
]
}
</code></pre>
<p>Priority levels are <code>low</code>, <code>normal</code>, <code>high</code>, and <code>critical</code>. Higher-priority behaviors take precedence when the institution must choose between actions.</p>
<h3 id="conditional-behaviors"><a class="header" href="#conditional-behaviors">Conditional Behaviors</a></h3>
<p>Some behaviors only activate under certain conditions:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
reputation: 0.85
uses behaviors: [
{ tree: ManageApprentices },
{ tree: NegotiateSuppliers, priority: high },
{ tree: EmergencyMeeting, when: reputation &lt; 0.3, priority: critical }
]
}
</code></pre>
<p>The <code>when</code> clause uses an <a href="../reference/17-expressions.html">expression</a>. Here, the emergency meeting behavior only activates when reputation drops below 0.3.</p>
<h3 id="institutions-with-schedules"><a class="header" href="#institutions-with-schedules">Institutions with Schedules</a></h3>
<p>Institutions can also follow schedules:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
uses schedule: GuildOperatingHours
}
</code></pre>
<p>For multiple schedules:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
uses schedules: [WeekdaySchedule, WeekendSchedule]
}
</code></pre>
<h3 id="prose-blocks-1"><a class="header" href="#prose-blocks-1">Prose Blocks</a></h3>
<p>Just like locations, institutions support prose blocks:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
members: 50
---description
The Bakers Guild has been the backbone of the town's bread trade
since 1892. Members share recipes, arrange apprenticeships, and
collectively negotiate flour prices.
---
---charter
Article I: All members shall maintain the highest standards.
Article II: Apprentices must complete a three-year program.
---
}
</code></pre>
<hr />
<h2 id="connecting-characters-to-institutions"><a class="header" href="#connecting-characters-to-institutions">Connecting Characters to Institutions</a></h2>
<p>Institutions do not have a built-in membership list. You model membership through character fields or relationships.</p>
<h3 id="through-character-fields"><a class="header" href="#through-character-fields">Through Character Fields</a></h3>
<p>The simplest approach add fields to your characters:</p>
<pre><code class="language-storybook">character Martha {
age: 45
occupation: baker
guild: BakersGuild
guild_role: guild_master
guild_member_since: "2005"
}
character Jane {
age: 19
occupation: apprentice_baker
guild: BakersGuild
guild_role: apprentice
guild_member_since: "2024"
}
</code></pre>
<h3 id="through-relationships"><a class="header" href="#through-relationships">Through Relationships</a></h3>
<p>For richer modeling, use relationships:</p>
<pre><code class="language-storybook">relationship GuildMembership {
Martha as guild_master { years_active: 20 }
BakersGuild as organization { }
bond: 0.95
}
relationship Apprenticeship {
Jane as apprentice { skills_learned: 12 }
Martha as mentor { patience_remaining: 0.7 }
BakersGuild as guild { }
years_completed: 1
}
</code></pre>
<p>This approach captures richer information: roles, duration, and multi-party connections.</p>
<hr />
<h2 id="locations-vs-institutions"><a class="header" href="#locations-vs-institutions">Locations vs. Institutions</a></h2>
<p>When should you use each?</p>
<div class="table-wrapper"><table><thead><tr><th>Question</th><th>Use…</th></tr></thead><tbody>
<tr><td>Where does something happen?</td><td>Location</td></tr>
<tr><td>Who or what organizes things?</td><td>Institution</td></tr>
<tr><td>Does it need behaviors?</td><td>Institution</td></tr>
<tr><td>Does it need a schedule?</td><td>Institution</td></tr>
<tr><td>Is it purely a place?</td><td>Location</td></tr>
<tr><td>Is it a group or organization?</td><td>Institution</td></tr>
</tbody></table>
</div>
<p>Sometimes the same concept needs both:</p>
<pre><code class="language-storybook">// The physical building
location BakersGuildHall {
type: guild_hall
address: "7 Guild Row"
capacity: 100
}
// The organization that meets there
institution BakersGuild {
type: trade_guild
members: 50
location: BakersGuildHall
uses behaviors: [
{ tree: ManageApprentices },
{ tree: NegotiateSuppliers }
]
uses schedule: GuildOperatingHours
}
</code></pre>
<p>The guild hall is a <em>place</em>. The guild is an <em>organization</em>. Keeping them separate lets you say “the guild meets at the guild hall” without conflating the building with the institution.</p>
<hr />
<h2 id="putting-it-all-together"><a class="header" href="#putting-it-all-together">Putting It All Together</a></h2>
<p>Here is a complete example showing how locations, institutions, and characters work together in the Baker family world:</p>
<pre><code class="language-storybook">// Enums for type safety
enum PlaceType {
bakery, residence, guild_hall, public_square
}
enum GuildRole {
guild_master, journeyman, apprentice
}
// Locations: where things happen
location BakersBakery {
type: bakery
address: "14 Main Street"
capacity: 30
owner: Martha
---description
Martha's artisan bakery on Main Street.
---
}
location BakerHome {
type: residence
address: "22 Elm Lane"
bedrooms: 4
residents: ["Martha", "David", "Jane", "Tom"]
}
location BakersGuildHall {
type: guild_hall
address: "7 Guild Row"
capacity: 100
---description
The historic Bakers Guild headquarters, established 1892.
---
}
// Institution: the organization
institution BakersGuild {
type: trade_guild
members: 50
founded: "1892"
reputation: 0.85
location: BakersGuildHall
leader: Martha
uses behaviors: [
{ tree: ManageApprentices, priority: normal },
{ tree: NegotiateSuppliers, priority: high },
{ tree: HostAnnualBakeOff, when: month is october }
]
uses schedule: GuildOperatingHours
---description
The Bakers Guild oversees apprenticeships, quality standards,
and the annual Great Bake-Off competition.
---
}
// Institution: the business
institution BakersBakeryBusiness {
type: business
owner: Martha
employees: 4
location: BakersBakery
uses behaviors: [
{ tree: DailyBakingOps, priority: high },
{ tree: InventoryManagement }
]
uses schedule: BakeryOperatingHours
}
// Characters connected to all of the above
character Martha {
age: 45
occupation: baker
workplace: BakersBakery
home: BakerHome
guild: BakersGuild
guild_role: guild_master
}
character Jane {
age: 19
occupation: apprentice_baker
workplace: BakersBakery
home: BakerHome
guild: BakersGuild
guild_role: apprentice
}
// Relationships tying it all together
relationship GuildLeadership {
Martha as guild_master { }
BakersGuild as guild { }
years_in_role: 8
}
relationship BakeryApprenticeship {
Jane as apprentice { }
Martha as mentor { }
BakersGuild as certifying_body { }
year: 1
total_years: 3
}
</code></pre>
<hr />
<h2 id="key-takeaways"><a class="header" href="#key-takeaways">Key Takeaways</a></h2>
<ol>
<li><strong>Locations</strong> are simple: name, fields, prose blocks. They model <em>places</em>.</li>
<li><strong>Institutions</strong> are richer: they add <code>uses behaviors</code> and <code>uses schedule</code> on top of fields and prose. They model <em>organizations</em>.</li>
<li><strong>Membership</strong> is modeled through character fields or relationships, not built into institution syntax.</li>
<li><strong>Separate place from organization</strong>: A guild hall (location) and the guild (institution) are different things.</li>
<li><strong>Use enums</strong> for type-safe categorization of locations and institutions.</li>
</ol>
<hr />
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<ul>
<li>Learn about <a href="../reference/17-expressions.html">expressions</a> used in conditional behavior links</li>
<li>Explore <a href="./03-first-behavior-tree.html">behavior trees</a> to create the behaviors your institutions use</li>
<li>See <a href="./07-schedules.html">schedules</a> to define operating hours for institutions</li>
<li>Read the full <a href="../reference/16a-locations.html">Locations Reference</a> and <a href="../reference/16b-institutions.html">Institutions Reference</a></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/08-life-arcs.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/09-overview.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/08-life-arcs.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/09-overview.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>