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,409 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Language Overview - 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="language-overview"><a class="header" href="#language-overview">Language Overview</a></h1>
<blockquote>
<p><strong>The Storybook language enables narrative simulation through structured declarations of characters, behaviors, relationships, and events.</strong></p>
</blockquote>
<h2 id="philosophy"><a class="header" href="#philosophy">Philosophy</a></h2>
<p>Storybook is a domain-specific language for narrative simulation, influenced by:</p>
<ul>
<li><strong>Rust</strong>: Strong typing, explicit declarations, and clear ownership semantics</li>
<li><strong>C#</strong>: Object-oriented patterns with declarative syntax</li>
<li><strong>Python</strong>: Readable, accessible syntax that prioritizes clarity</li>
</ul>
<p>The language balances <strong>technical precision</strong> with <strong>narrative expressiveness</strong>, making it accessible to storytellers while maintaining the rigor developers need.</p>
<h2 id="design-principles"><a class="header" href="#design-principles">Design Principles</a></h2>
<h3 id="1-code-as-narrative"><a class="header" href="#1-code-as-narrative">1. Code as Narrative</a></h3>
<p>Named nodes and prose blocks let code read like stories:</p>
<pre><code class="language-storybook">behavior Baker_MorningRoutine {
choose daily_priority {
then prepare_sourdough { ... }
then serve_customers { ... }
then restock_display { ... }
}
}
</code></pre>
<h3 id="2-explicit-is-better-than-implicit"><a class="header" href="#2-explicit-is-better-than-implicit">2. Explicit is Better Than Implicit</a></h3>
<p>Every declaration is self-documenting:</p>
<ul>
<li>Character fields show what defines them</li>
<li>Behavior trees show decision structures</li>
<li>Relationships name their participants</li>
</ul>
<h3 id="3-progressive-disclosure"><a class="header" href="#3-progressive-disclosure">3. Progressive Disclosure</a></h3>
<p>Simple cases are simple, complex cases are possible:</p>
<ul>
<li>Basic characters need just a name and fields</li>
<li>Templates enable inheritance and reuse</li>
<li>Advanced features (state machines, decorators) available when needed</li>
</ul>
<h3 id="4-semantic-validation"><a class="header" href="#4-semantic-validation">4. Semantic Validation</a></h3>
<p>The compiler catches narrative errors:</p>
<ul>
<li>Bond values must be 0.0..1.0</li>
<li>Schedule blocks cant overlap</li>
<li>Life arc transitions must reference valid states</li>
</ul>
<h2 id="language-structure"><a class="header" href="#language-structure">Language Structure</a></h2>
<h3 id="declaration-types"><a class="header" href="#declaration-types">Declaration Types</a></h3>
<p>Storybook has 10 top-level declaration types:</p>
<div class="table-wrapper"><table><thead><tr><th>Declaration</th><th>Purpose</th><th>Example</th></tr></thead><tbody>
<tr><td><code>character</code></td><td>Define entities with traits and behaviors</td><td>A baker with skills and schedule</td></tr>
<tr><td><code>template</code></td><td>Reusable patterns with ranges</td><td>A generic NPC template</td></tr>
<tr><td><code>behavior</code></td><td>Decision trees for actions</td><td>How a character responds to events</td></tr>
<tr><td><code>life_arc</code></td><td>State machines for life stages</td><td>Apprentice → Baker → Master</td></tr>
<tr><td><code>schedule</code></td><td>Time-based activities</td><td>Daily routine from 6am to 10pm</td></tr>
<tr><td><code>relationship</code></td><td>Connections between entities</td><td>Parent-child with bond values</td></tr>
<tr><td><code>institution</code></td><td>Organizations and groups</td><td>A bakery with employees</td></tr>
<tr><td><code>location</code></td><td>Places with properties</td><td>The town square</td></tr>
<tr><td><code>species</code></td><td>Type definitions with traits</td><td>Human vs Cat vs Rabbit</td></tr>
<tr><td><code>enum</code></td><td>Named value sets</td><td>EmotionalState options</td></tr>
</tbody></table>
</div>
<h3 id="value-types"><a class="header" href="#value-types">Value Types</a></h3>
<p>Fields can contain:</p>
<ul>
<li><strong>Primitives</strong>: <code>42</code>, <code>3.14</code>, <code>"text"</code>, <code>true</code></li>
<li><strong>Time</strong>: <code>08:30:00</code>, <code>14:15</code></li>
<li><strong>Duration</strong>: <code>2h30m</code>, <code>45s</code></li>
<li><strong>Ranges</strong>: <code>20..40</code> (for templates)</li>
<li><strong>Identifiers</strong>: <code>OtherCharacter</code>, <code>path::to::Thing</code></li>
<li><strong>Lists</strong>: <code>[1, 2, 3]</code></li>
<li><strong>Objects</strong>: <code>{ field: value }</code></li>
<li><strong>Prose blocks</strong>: <code> ---tag\nMulti-line\ntext\n---</code></li>
</ul>
<h3 id="expression-language"><a class="header" href="#expression-language">Expression Language</a></h3>
<p>Conditions and queries use:</p>
<ul>
<li><strong>Comparisons</strong>: <code>age &gt; 18</code>, <code>energy &lt;= 0.5</code></li>
<li><strong>Equality</strong>: <code>status is active</code>, <code>ready is true</code></li>
<li><strong>Logic</strong>: <code>tired and hungry</code>, <code>rich or lucky</code>, <code>not ready</code></li>
<li><strong>Field access</strong>: <code>self.health</code>, <code>other.bond</code></li>
<li><strong>Quantifiers</strong>: <code>forall x in children: x.happy</code></li>
</ul>
<h2 id="compilation-model"><a class="header" href="#compilation-model">Compilation Model</a></h2>
<h3 id="source--ast--sbir--runtime"><a class="header" href="#source--ast--sbir--runtime">Source → AST → SBIR → Runtime</a></h3>
<pre><code>.sb files → Parser → Abstract Syntax Tree → Resolver → SBIR Binary
</code></pre>
<p><strong>SBIR</strong> (Storybook Intermediate Representation) is a compact binary format that:</p>
<ul>
<li>Resolves all cross-file references</li>
<li>Validates semantic constraints</li>
<li>Optimizes for simulation runtime</li>
</ul>
<h3 id="validation-layers"><a class="header" href="#validation-layers">Validation Layers</a></h3>
<ol>
<li><strong>Lexical</strong>: Valid tokens and syntax</li>
<li><strong>Syntactic</strong>: Correct grammar structure</li>
<li><strong>Semantic</strong>: Type checking, reference resolution</li>
<li><strong>Domain</strong>: Narrative constraints (bond ranges, schedule overlaps)</li>
</ol>
<h2 id="file-organization"><a class="header" href="#file-organization">File Organization</a></h2>
<h3 id="project-structure"><a class="header" href="#project-structure">Project Structure</a></h3>
<pre><code>my-storybook/
├── characters/
│ ├── baker.sb
│ └── family.sb
├── behaviors/
│ └── daily_routine.sb
├── world/
│ ├── locations.sb
│ └── institutions.sb
└── schema/
├── species.sb
└── templates.sb
</code></pre>
<h3 id="import-system"><a class="header" href="#import-system">Import System</a></h3>
<p>Use <code>use</code> statements to reference definitions from other files:</p>
<pre><code class="language-storybook">use schema::species::Human;
use schema::templates::Adult;
character Baker: Human from Adult {
// ...
}
</code></pre>
<p><strong>Resolution order:</strong></p>
<ol>
<li>Same file</li>
<li>Explicitly imported</li>
<li>Error if not found</li>
</ol>
<h2 id="quick-reference"><a class="header" href="#quick-reference">Quick Reference</a></h2>
<h3 id="character-declaration"><a class="header" href="#character-declaration">Character Declaration</a></h3>
<pre><code class="language-storybook">character Name: Species from Template {
field: value
field: value
---prose_tag
Text content
---
}
</code></pre>
<h3 id="behavior-tree"><a class="header" href="#behavior-tree">Behavior Tree</a></h3>
<pre><code class="language-storybook">behavior Name {
choose label { // Selector
then label { ... } // Sequence
if (condition) // Condition
ActionName // Action
include path // Subtree
}
}
</code></pre>
<h3 id="life-arc"><a class="header" href="#life-arc">Life Arc</a></h3>
<pre><code class="language-storybook">life_arc Name {
state StateName {
on condition -&gt; NextState
}
}
</code></pre>
<h3 id="schedule"><a class="header" href="#schedule">Schedule</a></h3>
<pre><code class="language-storybook">schedule Name {
08:00 -&gt; 12:00: activity { }
12:00 -&gt; 13:00: lunch { }
}
</code></pre>
<h3 id="relationship"><a class="header" href="#relationship">Relationship</a></h3>
<pre><code class="language-storybook">relationship Name {
Person1 as role
Person2 as role
bond: 0.85
}
</code></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Dive deeper into each declaration type:</p>
<ul>
<li><a href="10-characters.html">Characters</a> - Detailed character syntax</li>
<li><a href="11-behavior-trees.html">Behavior Trees</a> - Complete behavior node reference</li>
<li><a href="12-decorators.html">Decorators</a> - All decorator types</li>
<li><a href="13-life-arcs.html">Life Arcs</a> - State machine semantics</li>
<li><a href="14-schedules.html">Schedules</a> - Time-based planning</li>
<li><a href="15-relationships.html">Relationships</a> - Connection modeling</li>
<li><a href="16-other-declarations.html">Other Declarations</a> - Templates, institutions, etc.</li>
<li><a href="17-expressions.html">Expressions</a> - Full expression language</li>
<li><a href="18-value-types.html">Value Types</a> - All field value types</li>
<li><a href="19-validation.html">Validation</a> - Error checking rules</li>
</ul>
<hr />
<p><strong>Philosophy Note</strong>: Storybook treats narrative as data. Characters arent objects with methods - theyre <strong>declarations</strong> of traits, connected by <strong>behaviors</strong> and <strong>relationships</strong>. This separation enables rich analysis, modification, and simulation of narrative worlds.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/09-locations-institutions.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/10-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="../tutorial/09-locations-institutions.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/10-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,611 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>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="characters"><a class="header" href="#characters">Characters</a></h1>
<p>Characters are the primary entities in Storybook—the people, creatures, and beings that inhabit your world. Each character has a set of attributes that define who they are, what they can do, and how they relate to the world around them.</p>
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;character-decl&gt; ::= "character" &lt;identifier&gt; &lt;species-clause&gt;? &lt;template-clause&gt;? &lt;body&gt;
&lt;species-clause&gt; ::= ":" &lt;qualified-path&gt;
&lt;template-clause&gt; ::= "from" &lt;qualified-path&gt; ("," &lt;qualified-path&gt;)*
&lt;body&gt; ::= "{" &lt;body-item&gt;* "}"
&lt;body-item&gt; ::= &lt;field&gt;
| &lt;uses-behaviors&gt;
| &lt;uses-schedule&gt;
&lt;uses-behaviors&gt; ::= "uses" "behaviors" ":" &lt;behavior-link-list&gt;
&lt;uses-schedule&gt; ::= "uses" ("schedule" | "schedules") ":" (&lt;qualified-path&gt; | &lt;schedule-list&gt;)
&lt;behavior-link-list&gt; ::= "[" &lt;behavior-link&gt; ("," &lt;behavior-link&gt;)* "]"
&lt;behavior-link&gt; ::= "{" &lt;behavior-link-field&gt;* "}"
&lt;behavior-link-field&gt; ::= "tree" ":" &lt;qualified-path&gt;
| "when" ":" &lt;expression&gt;
| "priority" ":" ("low" | "normal" | "high" | "critical")
&lt;schedule-list&gt; ::= "[" &lt;qualified-path&gt; ("," &lt;qualified-path&gt;)* "]"
&lt;field&gt; ::= &lt;identifier&gt; ":" &lt;value&gt;
&lt;value&gt; ::= &lt;literal&gt;
| &lt;qualified-path&gt;
| &lt;list&gt;
| &lt;object&gt;
| &lt;prose-block&gt;
| &lt;override&gt;
&lt;prose-block&gt; ::= "---" &lt;identifier&gt; &lt;content&gt; "---"
</code></pre>
<h2 id="components"><a class="header" href="#components">Components</a></h2>
<h3 id="name"><a class="header" href="#name">Name</a></h3>
<p>The characters identifier. Must be unique within its scope and follow standard identifier rules (alphanumeric + underscore, cannot start with digit).</p>
<h3 id="species-optional"><a class="header" href="#species-optional">Species (Optional)</a></h3>
<p>The species clause (<code>: SpeciesName</code>) defines what the character fundamentally <em>is</em>. This is distinct from templates, which define what attributes they <em>have</em>.</p>
<ul>
<li><strong>Purpose</strong>: Ontological typing—what kind of being this is</li>
<li><strong>Validation</strong>: Must reference a defined <code>species</code> declaration</li>
<li><strong>Single inheritance</strong>: A character can only have one species</li>
<li><strong>Default behavior</strong>: Species fields are inherited automatically</li>
</ul>
<p>Example:</p>
<pre><code class="language-storybook">character Martha: Human {
age: 34
}
</code></pre>
<h3 id="template-inheritance-optional"><a class="header" href="#template-inheritance-optional">Template Inheritance (Optional)</a></h3>
<p>The template clause (<code>from Template1, Template2</code>) specifies templates from which the character inherits fields. Templates provide reusable attribute sets.</p>
<ul>
<li><strong>Purpose</strong>: Compositional inheritance—mix-and-match capabilities and traits</li>
<li><strong>Multiple inheritance</strong>: Characters can inherit from multiple templates</li>
<li><strong>Merge semantics</strong>: Fields from later templates override earlier ones</li>
<li><strong>Override allowed</strong>: Character fields override all inherited fields</li>
</ul>
<p>Example:</p>
<pre><code class="language-storybook">character Martha: Human from Baker, BusinessOwner {
specialty: "sourdough"
}
</code></pre>
<h3 id="fields"><a class="header" href="#fields">Fields</a></h3>
<p>Fields define the characters attributes using the standard field syntax. All <a href="./18-value-types.html">value types</a> are supported.</p>
<p><strong>Common field categories:</strong></p>
<ul>
<li><strong>Physical traits</strong>: <code>height</code>, <code>weight</code>, <code>age</code>, <code>eye_color</code></li>
<li><strong>Personality</strong>: <code>confidence</code>, <code>patience</code>, <code>dedication</code></li>
<li><strong>Professional</strong>: <code>skill_level</code>, <code>specialty</code>, <code>recipes_mastered</code></li>
<li><strong>State tracking</strong>: <code>energy</code>, <code>mood</code>, <code>orders_today</code></li>
<li><strong>Capabilities</strong>: <code>can_teach</code>, <code>can_work_independently</code></li>
</ul>
<h3 id="prose-blocks"><a class="header" href="#prose-blocks">Prose Blocks</a></h3>
<p>Characters can contain multiple prose blocks for narrative content. Common tags:</p>
<ul>
<li><code>---backstory</code>: Character history and origin</li>
<li><code>---appearance</code>: Physical description</li>
<li><code>---personality</code>: Behavioral traits and quirks</li>
<li><code>---motivation</code>: Goals and desires</li>
<li><code>---secrets</code>: Hidden information</li>
</ul>
<p>Prose blocks are narrative-only and do not affect simulation logic.</p>
<h3 id="behavior-integration"><a class="header" href="#behavior-integration">Behavior Integration</a></h3>
<p>Characters can link to <a href="./14-behavior-trees.html">behavior trees</a> using the <code>uses behaviors</code> clause.</p>
<pre><code class="language-storybook">character Guard {
uses behaviors: [
{
tree: combat::patrol_route
priority: normal
},
{
tree: combat::engage_intruder
when: threat_detected
priority: high
}
]
}
</code></pre>
<p>Each behavior link includes:</p>
<ul>
<li><strong><code>tree</code></strong>: Qualified path to the behavior tree (required)</li>
<li><strong><code>when</code></strong>: Condition expression for activation (optional)</li>
<li><strong><code>priority</code></strong>: Execution priority (optional, default: <code>normal</code>)</li>
</ul>
<p>See <a href="./11-behavior-trees.html">Behavior Trees</a> for details on behavior tree syntax and semantics.</p>
<h3 id="schedule-integration"><a class="header" href="#schedule-integration">Schedule Integration</a></h3>
<p>Characters can follow <a href="./16-schedules.html">schedules</a> using the <code>uses schedule</code> or <code>uses schedules</code> clause.</p>
<pre><code class="language-storybook">character Baker {
uses schedule: BakerySchedule
}
character Innkeeper {
uses schedules: [WeekdaySchedule, WeekendSchedule]
}
</code></pre>
<ul>
<li>Single schedule: <code>uses schedule: ScheduleName</code></li>
<li>Multiple schedules: <code>uses schedules: [Schedule1, Schedule2]</code></li>
</ul>
<p>The runtime selects the appropriate schedule based on temporal constraints. See <a href="./14-schedules.html">Schedules</a> for composition and selection semantics.</p>
<h2 id="species-vs-templates"><a class="header" href="#species-vs-templates">Species vs. Templates</a></h2>
<p>The distinction between species (<code>:</code>) and templates (<code>from</code>) reflects ontological vs. compositional typing:</p>
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Species (<code>:</code>)</th><th>Templates (<code>from</code>)</th></tr></thead><tbody>
<tr><td><strong>Semantics</strong></td><td>What the character <em>is</em></td><td>What the character <em>has</em></td></tr>
<tr><td><strong>Cardinality</strong></td><td>Exactly one</td><td>Zero or more</td></tr>
<tr><td><strong>Example</strong></td><td><code>: Human</code>, <code>: Dragon</code></td><td><code>from Warrior, Mage</code></td></tr>
<tr><td><strong>Purpose</strong></td><td>Fundamental nature</td><td>Reusable trait sets</td></tr>
<tr><td><strong>Override</strong></td><td>Can override species fields</td><td>Can override template fields</td></tr>
</tbody></table>
</div>
<p>Example showing both:</p>
<pre><code class="language-storybook">species Dragon {
max_lifespan: 1000
can_fly: true
}
template Hoarder {
treasure_value: 0..1000000
greed_level: 0.0..1.0
}
template Ancient {
age: 500..1000
wisdom: 0.8..1.0
}
character Smaug: Dragon from Hoarder, Ancient {
age: 850
treasure_value: 500000
greed_level: 0.95
}
</code></pre>
<h2 id="field-resolution-order"><a class="header" href="#field-resolution-order">Field Resolution Order</a></h2>
<p>When a character inherits from species and templates, fields are resolved in this order (later overrides earlier):</p>
<ol>
<li><strong>Species fields</strong> (base ontology)</li>
<li><strong>Template fields</strong> (left to right in <code>from</code> clause)</li>
<li><strong>Character fields</strong> (highest priority)</li>
</ol>
<p>Example:</p>
<pre><code class="language-storybook">species Human {
lifespan: 80
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 fields:
// lifespan: 80 (from Human)
// speed: 2.0 (Berserker overrides Warrior overrides Human)
// strength: 20 (character overrides Berserker)
</code></pre>
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<p>The Storybook compiler enforces these validation rules:</p>
<ol>
<li><strong>Unique names</strong>: Character names must be unique within their module</li>
<li><strong>Species exists</strong>: If specified, the species must reference a defined <code>species</code> declaration</li>
<li><strong>Templates exist</strong>: All templates in the <code>from</code> clause must reference defined <code>template</code> declarations</li>
<li><strong>No circular inheritance</strong>: Templates cannot form circular dependency chains</li>
<li><strong>Field type consistency</strong>: Field values must match expected types from species/templates</li>
<li><strong>Reserved fields</strong>: Cannot use reserved keywords as field names</li>
<li><strong>Behavior trees exist</strong>: All behavior tree references must resolve to defined <code>behavior</code> declarations</li>
<li><strong>Schedules exist</strong>: All schedule references must resolve to defined <code>schedule</code> declarations</li>
<li><strong>Prose tag uniqueness</strong>: Each prose tag can appear at most once per character</li>
</ol>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
<h3 id="basic-character"><a class="header" href="#basic-character">Basic Character</a></h3>
<pre><code class="language-storybook">character SimpleMerchant {
name: "Gregor"
occupation: "Fish Merchant"
wealth: 50
---personality
A straightforward fish seller at the market. Honest, hardworking,
and always smells faintly of mackerel.
---
}
</code></pre>
<h3 id="character-with-species"><a class="header" href="#character-with-species">Character with Species</a></h3>
<pre><code class="language-storybook">character Martha: Human {
age: 34
skill_level: 0.95
specialty: "sourdough"
---backstory
Martha learned to bake from her grandmother, starting at age
twelve. She now runs the most popular bakery in town.
---
}
</code></pre>
<h3 id="character-with-template-inheritance"><a class="header" href="#character-with-template-inheritance">Character with Template Inheritance</a></h3>
<pre><code class="language-storybook">character Jane: Human from Baker, PastrySpecialist {
age: 36
specialty: "pastries"
recipes_mastered: 120
years_experience: 18
can_teach: true
---appearance
A focused woman with flour-dusted apron and steady hands.
Known for her intricate pastry decorations and precise
temperature control.
---
}
</code></pre>
<h3 id="character-with-all-features"><a class="header" href="#character-with-all-features">Character with All Features</a></h3>
<pre><code class="language-storybook">character CityGuard: Human from CombatTraining, LawEnforcement {
age: 30
rank: "Sergeant"
// Physical traits
height: 175
strength: 12
// Equipment
has_weapon: true
armor_level: 2
// Behavior integration
uses behaviors: [
{
tree: guards::patrol_route
priority: normal
},
{
tree: guards::engage_hostile
when: threat_detected
priority: high
},
{
tree: guards::sound_alarm
when: emergency
priority: critical
}
]
// Schedule integration
uses schedules: [guards::day_shift, guards::night_shift]
---backstory
A veteran of the city watch, now responsible for training new recruits
while maintaining order in the merchant district.
---
---personality
Gruff exterior with a hidden soft spot for street children. Follows
the rules but knows when to look the other way.
---
}
</code></pre>
<h3 id="character-with-overrides"><a class="header" href="#character-with-overrides">Character with Overrides</a></h3>
<pre><code class="language-storybook">template WeaponUser {
damage: 5..15
accuracy: 0.7
}
character MasterSwordsman: Human from WeaponUser {
// Override template range with specific value
damage: 15
accuracy: 0.95
// Add character-specific fields
signature_move: "Whirlwind Strike"
}
</code></pre>
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
<h3 id="protagonist-definition"><a class="header" href="#protagonist-definition">Protagonist Definition</a></h3>
<p>Define rich, dynamic protagonists with complex attributes:</p>
<pre><code class="language-storybook">character Elena: Human from Scholar, Diplomat {
age: 28
intelligence: 18
charisma: 16
languages_known: ["Common", "Elvish", "Draconic"]
books_read: 347
current_quest: "Broker peace between warring kingdoms"
---backstory
Raised in the Grand Library, Elena discovered ancient texts that
hinted at a forgotten alliance between humans and dragons. She now
seeks to revive that alliance to end the current war.
---
}
</code></pre>
<h3 id="npc-templates"><a class="header" href="#npc-templates">NPC Templates</a></h3>
<p>Create diverse NPCs from templates:</p>
<pre><code class="language-storybook">template Villager {
occupation: "Farmer"
wealth: 10..50
disposition: 0.0..1.0 // 0=hostile, 1=friendly
}
character Oswald: Human from Villager {
occupation: "Blacksmith"
wealth: 45
disposition: 0.8
}
character Mildred: Human from Villager {
occupation: "Baker"
wealth: 35
disposition: 0.95
}
</code></pre>
<h3 id="ensemble-casts"><a class="header" href="#ensemble-casts">Ensemble Casts</a></h3>
<p>Define multiple related characters:</p>
<pre><code class="language-storybook">template BakeryStaff {
punctuality: 0.5..1.0
teamwork: 0.5..1.0
}
template Apprentice {
skill_level: 0.0..0.5
dedication: 0.5..1.0
}
character Elena: Human from BakeryStaff, Apprentice {
age: 16
natural_talent: 0.8
dedication: 0.9
---backstory
Elena comes from a family of farmers who could never afford to
buy bread from the bakery. When Martha offered her an apprenticeship,
she jumped at the chance to learn a trade.
---
}
</code></pre>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./16-other-declarations.html#species">Species</a> - Species declarations</li>
<li><a href="./16-other-declarations.html#templates">Templates</a> - Template definitions and strict mode</li>
<li><a href="./18-value-types.html">Value Types</a> - All supported value types</li>
<li><a href="./11-behavior-trees.html">Behavior Trees</a> - Character behavior integration</li>
<li><a href="./14-schedules.html">Schedules</a> - Character schedule integration</li>
<li><a href="./15-relationships.html">Relationships</a> - Relationships between characters</li>
<li><a href="./13-life-arcs.html">Life Arcs</a> - Character state machines over time</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Instantiation</strong>: Characters are concrete instances; they cannot be instantiated further</li>
<li><strong>Composition</strong>: Prefer template composition over deep species hierarchies</li>
<li><strong>Modularity</strong>: Characters can reference behaviors and schedules from other modules</li>
<li><strong>Narrative-driven</strong>: Use prose blocks to embed storytelling directly with data</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/09-overview.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/11-behavior-trees.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../reference/09-overview.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/11-behavior-trees.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,883 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Behavior Trees - Storybook Language Guide</title>
<!-- Custom HTML head -->
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Storybook Language Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="behavior-trees"><a class="header" href="#behavior-trees">Behavior Trees</a></h1>
<p>Behavior trees define the decision-making logic for characters, institutions, and other entities. They model how an entity chooses actions, responds to conditions, and adapts to its environment. Storybook uses behavior trees for character AI, NPC routines, and complex reactive behavior.</p>
<h2 id="what-is-a-behavior-tree"><a class="header" href="#what-is-a-behavior-tree">What is a Behavior Tree?</a></h2>
<p>A behavior tree is a hierarchical structure that executes from root to leaves, making decisions based on success/failure of child nodes:</p>
<ul>
<li><strong>Composite nodes</strong> (choose, then) have multiple children and control flow</li>
<li><strong>Condition nodes</strong> (if, when) test predicates</li>
<li><strong>Action nodes</strong> execute concrete behaviors</li>
<li><strong>Decorator nodes</strong> (repeat, invert, timeout, etc.) modify child behavior</li>
<li><strong>Subtree nodes</strong> (include) reference other behavior trees</li>
</ul>
<p>Behavior trees are evaluated every tick (frame), flowing through the tree from root to leaves until a node returns success or failure.</p>
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;behavior-decl&gt; ::= "behavior" &lt;identifier&gt; &lt;body&gt;
&lt;body&gt; ::= "{" &lt;prose-blocks&gt;* &lt;behavior-node&gt; "}"
&lt;behavior-node&gt; ::= &lt;selector&gt;
| &lt;sequence&gt;
| &lt;condition&gt;
| &lt;action&gt;
| &lt;decorator&gt;
| &lt;subtree&gt;
&lt;selector&gt; ::= "choose" &lt;label&gt;? "{" &lt;behavior-node&gt;+ "}"
&lt;sequence&gt; ::= "then" &lt;label&gt;? "{" &lt;behavior-node&gt;+ "}"
&lt;condition&gt; ::= ("if" | "when") "(" &lt;expression&gt; ")"
&lt;action&gt; ::= &lt;identifier&gt; ( "(" &lt;param-list&gt; ")" )?
&lt;param-list&gt; ::= &lt;field&gt; ("," &lt;field&gt;)*
&lt;decorator&gt; ::= &lt;decorator-type&gt; ("(" &lt;params&gt; ")")? "{" &lt;behavior-node&gt; "}"
&lt;subtree&gt; ::= "include" &lt;qualified-path&gt;
&lt;label&gt; ::= &lt;identifier&gt;
</code></pre>
<h2 id="composite-nodes"><a class="header" href="#composite-nodes">Composite Nodes</a></h2>
<h3 id="selector-choose"><a class="header" href="#selector-choose">Selector: <code>choose</code></a></h3>
<p>A selector tries its children in order until one succeeds. Returns success if any child succeeds, failure if all fail.</p>
<p><strong>Execution:</strong></p>
<ol>
<li>Try first child</li>
<li>If succeeds -&gt; return success</li>
<li>If fails -&gt; try next child</li>
<li>If all fail -&gt; return failure</li>
</ol>
<p><strong>Use case:</strong> “Try A, if that fails try B, if that fails try C…”</p>
<pre><code class="language-storybook">behavior GuardBehavior {
choose guard_actions {
AttackIntruder // Try first
SoundAlarm // If attack fails, sound alarm
FleeInPanic // If alarm fails, flee
}
}
</code></pre>
<p><strong>Named selectors:</strong></p>
<pre><code class="language-storybook">choose service_options {
then serve_regular {
CustomerIsWaiting
TakeOrder
}
then restock_display {
DisplayIsLow
FetchFromKitchen
}
}
</code></pre>
<p>Labels are optional but recommended for complex treesthey improve readability and debugging.</p>
<h3 id="sequence-then"><a class="header" href="#sequence-then">Sequence: <code>then</code></a></h3>
<p>A sequence runs its children in order until one fails. Returns success only if all children succeed, failure if any fails.</p>
<p><strong>Execution:</strong></p>
<ol>
<li>Run first child</li>
<li>If fails -&gt; return failure</li>
<li>If succeeds -&gt; run next child</li>
<li>If all succeed -&gt; return success</li>
</ol>
<p><strong>Use case:</strong> “Do A, then B, then C… all must succeed”</p>
<pre><code class="language-storybook">behavior BrewingSequence {
then brew_potion {
GatherIngredients // Must succeed
MixIngredients // Then this must succeed
Boil // Then this must succeed
BottlePotion // Finally this
}
}
</code></pre>
<p><strong>Named sequences:</strong></p>
<pre><code class="language-storybook">then prepare_sourdough {
MixDough
KneadDough
ShapeLoaves
}
</code></pre>
<h3 id="nesting-composites"><a class="header" href="#nesting-composites">Nesting Composites</a></h3>
<p>Composite nodes can nest arbitrarily deep:</p>
<pre><code class="language-storybook">behavior ComplexAI {
choose root_decision {
// Combat branch
then engage_combat {
if(enemy_nearby)
choose combat_tactics {
AttackWithSword
AttackWithMagic
DefendAndWait
}
}
// Exploration branch
then explore_area {
if(safe)
choose exploration_mode {
SearchForTreasure
MapTerritory
Rest
}
}
// Default: Idle
Idle
}
}
</code></pre>
<h2 id="condition-nodes"><a class="header" href="#condition-nodes">Condition Nodes</a></h2>
<p>Conditions test expressions and return success/failure based on the result.</p>
<h3 id="if-vs-when"><a class="header" href="#if-vs-when"><code>if</code> vs. <code>when</code></a></h3>
<p>Both are semantically identicaluse whichever reads better in context:</p>
<pre><code class="language-storybook">behavior Example {
choose options {
// "if" for state checks
then branch_a {
if(player_nearby)
Attack
}
// "when" for event-like conditions
then branch_b {
when(alarm_triggered)
Flee
}
}
}
</code></pre>
<h3 id="condition-syntax"><a class="header" href="#condition-syntax">Condition Syntax</a></h3>
<pre><code class="language-storybook">if(health &lt; 20) // Comparison
when(is_hostile) // Boolean field
if(distance &gt; 10 and has_weapon) // Logical AND
when(not is_stunned or is_enraged) // Logical OR with NOT
</code></pre>
<p>See <a href="./17-expressions.html">Expression Language</a> for complete expression syntax.</p>
<h2 id="action-nodes"><a class="header" href="#action-nodes">Action Nodes</a></h2>
<p>Actions are leaf nodes that execute concrete behaviors. They reference action implementations in the runtime.</p>
<h3 id="basic-actions"><a class="header" href="#basic-actions">Basic Actions</a></h3>
<pre><code class="language-storybook">behavior SimpleActions {
then do_things {
MoveForward
TurnLeft
Attack
}
}
</code></pre>
<h3 id="actions-with-parameters"><a class="header" href="#actions-with-parameters">Actions with Parameters</a></h3>
<p>Actions can have named parameters using parenthesis syntax:</p>
<pre><code class="language-storybook">behavior ParameterizedActions {
then patrol {
MoveTo(destination: "Waypoint1", speed: 1.5)
WaitFor(duration: 5s)
MoveTo(destination: "Waypoint2", speed: 1.5)
}
}
</code></pre>
<p><strong>Parameter passing:</strong></p>
<ul>
<li>Parameters are passed as fields (name: value)</li>
<li>All <a href="./18-value-types.html">value types</a> supported</li>
<li>Runtime validates parameter types</li>
</ul>
<pre><code class="language-storybook">behavior RichParameters {
then complex_action {
CastSpell(spell: "Fireball", target: enemy_position, power: 75, multicast: false)
Heal(amount: 50, target: self)
}
}
</code></pre>
<h2 id="decorator-nodes"><a class="header" href="#decorator-nodes">Decorator Nodes</a></h2>
<p>Decorators wrap a single child node and modify its behavior. See <a href="./12-decorators.html">Decorators</a> for complete reference.</p>
<h3 id="common-decorators"><a class="header" href="#common-decorators">Common Decorators</a></h3>
<pre><code class="language-storybook">behavior DecoratorExamples {
choose {
// Repeat infinitely
repeat {
PatrolRoute
}
// Repeat exactly 3 times
repeat(3) {
CheckDoor
}
// Repeat 2 to 5 times
repeat(2..5) {
SearchArea
}
// Invert success/failure
invert {
IsEnemyNearby // Returns success if enemy NOT nearby
}
// Retry up to 5 times on failure
retry(5) {
OpenLockedDoor
}
// Timeout after 10 seconds
timeout(10s) {
SolveComplexPuzzle
}
// Cooldown: only run once per 30 seconds
cooldown(30s) {
FireCannon
}
// If: only run if condition true
if(health &gt; 50) {
AggressiveAttack
}
// Always succeed regardless of child result
succeed_always {
AttemptOptionalTask
}
// Always fail regardless of child result
fail_always {
ImpossipleTask
}
}
}
</code></pre>
<h2 id="subtree-references"><a class="header" href="#subtree-references">Subtree References</a></h2>
<p>The <code>include</code> keyword references another behavior tree, enabling modularity and reuse.</p>
<h3 id="basic-subtree"><a class="header" href="#basic-subtree">Basic Subtree</a></h3>
<pre><code class="language-storybook">behavior PatrolRoute {
then patrol {
MoveTo(destination: "Waypoint1")
MoveTo(destination: "Waypoint2")
MoveTo(destination: "Waypoint3")
}
}
behavior GuardBehavior {
choose guard_ai {
then combat {
if(enemy_nearby)
include combat::engage
}
include PatrolRoute // Reuse patrol behavior
}
}
</code></pre>
<h3 id="qualified-subtree-references"><a class="header" href="#qualified-subtree-references">Qualified Subtree References</a></h3>
<pre><code class="language-storybook">behavior ComplexAI {
choose ai_modes {
include behaviors::combat::melee
include behaviors::combat::ranged
include behaviors::exploration::search
include behaviors::social::greet
}
}
</code></pre>
<h2 id="named-nodes"><a class="header" href="#named-nodes">Named Nodes</a></h2>
<p>Both composite nodes (<code>choose</code>, <code>then</code>) can have optional labels:</p>
<pre><code class="language-storybook">behavior NamedNodeExample {
choose daily_priority { // Named selector
then handle_special_orders { // Named sequence
CheckOrderQueue
PrepareSpecialIngredients
BakeSpecialItem
}
choose regular_work { // Named selector
then morning_bread { // Named sequence
MixSourdough
BakeLoaves
}
}
}
}
</code></pre>
<p><strong>Benefits:</strong></p>
<ul>
<li><strong>Readability</strong>: Tree structure is self-documenting</li>
<li><strong>Debugging</strong>: Named nodes appear in execution traces</li>
<li><strong>Narrative</strong>: Labels can be narrative (“handle_special_orders” vs. anonymous node)</li>
</ul>
<p><strong>Guidelines:</strong></p>
<ul>
<li>Use labels for important structural nodes</li>
<li>Use descriptive, narrative names</li>
<li>Omit labels for trivial nodes</li>
</ul>
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
<h3 id="simple-guard-ai"><a class="header" href="#simple-guard-ai">Simple Guard AI</a></h3>
<pre><code class="language-storybook">behavior GuardPatrol {
choose guard_logic {
// Combat if enemy nearby
then combat_mode {
if(enemy_nearby and has_weapon)
Attack
}
// Sound alarm if see intruder
then alarm_mode {
if(intruder_detected)
SoundAlarm
}
// Default: Patrol
then patrol_mode {
include PatrolRoute
}
}
}
</code></pre>
<h3 id="complex-npc-behavior"><a class="header" href="#complex-npc-behavior">Complex NPC Behavior</a></h3>
<pre><code class="language-storybook">behavior Innkeeper_DailyRoutine {
---description
The innkeeper's behavior throughout the day. Uses selectors for
decision-making and sequences for multi-step actions.
---
choose daily_activity {
// Morning: Prepare for opening
then morning_prep {
when(time_is_morning)
then prep_sequence {
UnlockDoor
LightFireplace
PrepareBreakfast
WaitForGuests
}
}
// Day: Serve customers
then day_service {
when(time_is_daytime)
choose service_mode {
// Serve customer if present
then serve {
if(customer_waiting)
GreetCustomer
TakeOrder
ServeMeal
CollectPayment
}
// Restock if needed
then restock {
if(inventory_low)
ReplenishInventory
}
// Default: Clean
CleanTables
}
}
// Night: Close up
then evening_close {
when(time_is_evening)
then close_sequence {
DismissRemainingGuests
LockDoor
CountMoney
GoToBed
}
}
}
}
</code></pre>
<h3 id="morning-baking-routine"><a class="header" href="#morning-baking-routine">Morning Baking Routine</a></h3>
<pre><code class="language-storybook">behavior Baker_MorningRoutine {
---description
Martha's morning routine: prepare dough step by step,
from mixing to shaping to baking.
---
then morning_baking {
// Start with sourdough
then prepare_starter {
CheckStarter
FeedStarter
WaitForActivity
}
// Mix the dough
then mix_dough {
MeasureFlour
AddWater
IncorporateStarter
}
// Knead and shape
then shape_loaves {
KneadDough
FirstRise
ShapeLoaves
}
// Bake
then bake {
PreHeatOven
LoadLoaves
MonitorBaking
}
}
}
</code></pre>
<h3 id="repeating-behavior"><a class="header" href="#repeating-behavior">Repeating Behavior</a></h3>
<pre><code class="language-storybook">behavior Bakery_CustomerServiceLoop {
---description
The bakery's continuous customer service loop. Uses infinite
repeat decorator to serve customers throughout the day.
---
repeat {
then service_cycle {
// Check for customers
choose service_mode {
then serve_waiting {
if(customer_waiting)
GreetCustomer
TakeOrder
}
then restock_display {
if(display_low)
FetchFromKitchen
ArrangeOnShelves
}
}
// Process payment
CollectPayment
ThankCustomer
// Brief pause between customers
timeout(5s) {
CleanCounter
}
PrepareForNextCustomer
}
}
}
</code></pre>
<h3 id="retry-logic-for-delicate-recipes"><a class="header" href="#retry-logic-for-delicate-recipes">Retry Logic for Delicate Recipes</a></h3>
<pre><code class="language-storybook">behavior Baker_DelicatePastry {
---description
Elena attempting a delicate pastry recipe that requires
precise technique. Uses retry decorator for persistence.
---
choose baking_strategy {
// Try when conditions are right
then attempt_pastry {
if(oven_at_temperature)
// Try up to 3 times
retry(3) {
then make_pastry {
RollDoughThin
ApplyFilling
FoldAndSeal
CheckForLeaks
}
}
}
// If oven not ready, prepare meanwhile
then prepare_meanwhile {
if(not oven_at_temperature)
then prep_sequence {
PrepareIngredients
MixFilling
ChillDough
}
}
}
}
</code></pre>
<h2 id="integration-with-characters"><a class="header" href="#integration-with-characters">Integration with Characters</a></h2>
<p>Characters link to behaviors using the <code>uses behaviors</code> clause:</p>
<pre><code class="language-storybook">character Guard {
uses behaviors: [
{
tree: guards::patrol_route
priority: normal
},
{
tree: guards::engage_hostile
when: threat_detected
priority: high
},
{
tree: guards::sound_alarm
when: emergency
priority: critical
}
]
}
</code></pre>
<p><strong>Behavior selection:</strong></p>
<ul>
<li>Multiple behaviors evaluated by priority (critical &gt; high &gt; normal &gt; low)</li>
<li>Conditions (<code>when</code> clause) gate behavior activation</li>
<li>Higher-priority behaviors preempt lower-priority ones</li>
</ul>
<p>See <a href="./10-characters.html#behavior-integration">Characters</a> for complete behavior linking syntax.</p>
<h2 id="execution-semantics"><a class="header" href="#execution-semantics">Execution Semantics</a></h2>
<h3 id="tick-based-evaluation"><a class="header" href="#tick-based-evaluation">Tick-Based Evaluation</a></h3>
<p>Behavior trees execute every “tick” (typically once per frame):</p>
<ol>
<li><strong>Start at root</strong> node</li>
<li><strong>Traverse</strong> down to leaves based on composite logic</li>
<li><strong>Execute</strong> leaf nodes (conditions, actions)</li>
<li><strong>Return</strong> success/failure up the tree</li>
<li><strong>Repeat</strong> next tick</li>
</ol>
<h3 id="node-return-values"><a class="header" href="#node-return-values">Node Return Values</a></h3>
<p>Every node returns one of:</p>
<ul>
<li><strong>Success</strong>: Node completed successfully</li>
<li><strong>Failure</strong>: Node failed</li>
<li><strong>Running</strong>: Node still executing (async action)</li>
</ul>
<h3 id="stateful-vs-stateless"><a class="header" href="#stateful-vs-stateless">Stateful vs. Stateless</a></h3>
<ul>
<li><strong>Stateless nodes</strong>: Evaluate fresh every tick (conditions, simple actions)</li>
<li><strong>Stateful nodes</strong>: Maintain state across ticks (decorators, long actions)</li>
</ul>
<p>Example of stateful behavior:</p>
<pre><code class="language-storybook">behavior LongAction {
timeout(30s) { // Stateful: tracks elapsed time
ComplexCalculation // May take multiple ticks
}
}
</code></pre>
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>At least one node</strong>: Behavior body must contain at least one node</li>
<li><strong>Composite children</strong>: <code>choose</code> and <code>then</code> require at least one child</li>
<li><strong>Decorator child</strong>: Decorators require exactly one child</li>
<li><strong>Action exists</strong>: Action names must reference registered actions in runtime</li>
<li><strong>Subtree exists</strong>: <code>include</code> must reference a defined <code>behavior</code> declaration</li>
<li><strong>Expression validity</strong>: Condition expressions must be well-formed</li>
<li><strong>Duration format</strong>: Decorator durations must be valid (e.g., <code>5s</code>, <code>10m</code>)</li>
<li><strong>Unique labels</strong>: Node labels (if used) should be unique within their parent</li>
<li><strong>Parameter types</strong>: Action parameters must match expected types</li>
</ol>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-prefer-shallow-trees"><a class="header" href="#1-prefer-shallow-trees">1. Prefer Shallow Trees</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">choose {
then { then { then { then { Action } } } } // Too deep!
}
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">choose root {
include combat_tree
include exploration_tree
include social_tree
}
</code></pre>
<h3 id="2-use-named-nodes-for-clarity"><a class="header" href="#2-use-named-nodes-for-clarity">2. Use Named Nodes for Clarity</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">choose {
then {
IsHungry
FindFood
Eat
}
Wander
}
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">choose survival {
then eat_if_hungry {
IsHungry
FindFood
Eat
}
Wander
}
</code></pre>
<h3 id="3-subtrees-for-reusability"><a class="header" href="#3-subtrees-for-reusability">3. Subtrees for Reusability</a></h3>
<p><strong>Avoid:</strong> Duplicating logic across behaviors</p>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">behavior Combat_Common {
then attack_sequence {
DrawWeapon
Approach
Strike
}
}
behavior Warrior {
include Combat_Common
}
behavior Guard {
include Combat_Common
}
</code></pre>
<h3 id="4-decorators-for-timing"><a class="header" href="#4-decorators-for-timing">4. Decorators for Timing</a></h3>
<p><strong>Avoid:</strong> Manual timing in actions</p>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">timeout(10s) {
ComplexTask
}
cooldown(30s) {
SpecialAbility
}
</code></pre>
<h3 id="5-guard-for-preconditions"><a class="header" href="#5-guard-for-preconditions">5. Guard for Preconditions</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">then problematic {
ExpensiveAction // Always runs even if inappropriate
}
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">if(can_afford_action) {
ExpensiveAction // Only runs when condition passes
}
</code></pre>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./12-decorators.html">Decorators</a> - Complete decorator reference</li>
<li><a href="./10-characters.html">Characters</a> - Linking behaviors to characters</li>
<li><a href="./17-expressions.html">Expression Language</a> - Condition expression syntax</li>
<li><a href="./18-value-types.html">Value Types</a> - Parameter value types</li>
<li><a href="../advanced/20-patterns.html">Design Patterns</a> - Common behavior tree patterns</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Reactive AI</strong>: Behavior trees continuously react to changing conditions</li>
<li><strong>Hierarchical decision-making</strong>: Composite nodes create decision hierarchies</li>
<li><strong>Modularity</strong>: Subtrees enable behavior reuse and composition</li>
<li><strong>Narrative-driven design</strong>: Named nodes make behavior trees readable as stories</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/10-characters.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../reference/12-decorators.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../reference/10-characters.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../reference/12-decorators.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>

View File

@@ -0,0 +1,837 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Decorators - 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="decorators"><a class="header" href="#decorators">Decorators</a></h1>
<p>Decorators are special nodes that wrap a single child and modify its execution behavior. They enable timing control, retry logic, conditional execution, and result inversion without modifying the child node itself.</p>
<h2 id="what-are-decorators"><a class="header" href="#what-are-decorators">What Are Decorators?</a></h2>
<p>Decorators sit between a parent and child node, transforming the childs behavior:</p>
<pre><code>Parent
└─ Decorator
└─ Child
</code></pre>
<p>The decorator intercepts the childs execution, potentially:</p>
<ul>
<li>Repeating it multiple times</li>
<li>Timing it out</li>
<li>Inverting its result</li>
<li>Guarding its execution</li>
<li>Forcing a specific result</li>
</ul>
<h2 id="decorator-types"><a class="header" href="#decorator-types">Decorator Types</a></h2>
<p>Storybook provides 10 decorator types:</p>
<div class="table-wrapper"><table><thead><tr><th>Decorator</th><th>Purpose</th><th>Example</th></tr></thead><tbody>
<tr><td><code>repeat</code></td><td>Loop infinitely</td><td><code>repeat { Patrol }</code></td></tr>
<tr><td><code>repeat(N)</code></td><td>Loop N times</td><td><code>repeat(3) { Check }</code></td></tr>
<tr><td><code>repeat(min..max)</code></td><td>Loop min to max times</td><td><code>repeat(2..5) { Search }</code></td></tr>
<tr><td><code>invert</code></td><td>Flip success/failure</td><td><code>invert { IsEnemy }</code></td></tr>
<tr><td><code>retry(N)</code></td><td>Retry on failure (max N times)</td><td><code>retry(5) { Open }</code></td></tr>
<tr><td><code>timeout(duration)</code></td><td>Fail after duration</td><td><code>timeout(10s) { Solve }</code></td></tr>
<tr><td><code>cooldown(duration)</code></td><td>Run at most once per duration</td><td><code>cooldown(30s) { Fire }</code></td></tr>
<tr><td><code>if(condition)</code></td><td>Only run if condition true</td><td><code>if(health &gt; 50) { Attack }</code></td></tr>
<tr><td><code>succeed_always</code></td><td>Always return success</td><td><code>succeed_always { Try }</code></td></tr>
<tr><td><code>fail_always</code></td><td>Always return failure</td><td><code>fail_always { Test }</code></td></tr>
</tbody></table>
</div>
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;decorator&gt; ::= &lt;repeat-decorator&gt;
| &lt;invert-decorator&gt;
| &lt;retry-decorator&gt;
| &lt;timeout-decorator&gt;
| &lt;cooldown-decorator&gt;
| &lt;if-decorator&gt;
| &lt;force-result-decorator&gt;
&lt;repeat-decorator&gt; ::= "repeat" &lt;repeat-spec&gt;? "{" &lt;behavior-node&gt; "}"
&lt;repeat-spec&gt; ::= "(" &lt;number&gt; ")"
| "(" &lt;number&gt; ".." &lt;number&gt; ")"
&lt;invert-decorator&gt; ::= "invert" "{" &lt;behavior-node&gt; "}"
&lt;retry-decorator&gt; ::= "retry" "(" &lt;number&gt; ")" "{" &lt;behavior-node&gt; "}"
&lt;timeout-decorator&gt; ::= "timeout" "(" &lt;duration-literal&gt; ")" "{" &lt;behavior-node&gt; "}"
&lt;cooldown-decorator&gt; ::= "cooldown" "(" &lt;duration-literal&gt; ")" "{" &lt;behavior-node&gt; "}"
&lt;if-decorator&gt; ::= "if" "(" &lt;expression&gt; ")" "{" &lt;behavior-node&gt; "}"
&lt;force-result-decorator&gt; ::= ("succeed_always" | "fail_always") "{" &lt;behavior-node&gt; "}"
&lt;duration-literal&gt; ::= &lt;number&gt; ("s" | "m" | "h" | "d")
</code></pre>
<h2 id="repeat-decorators"><a class="header" href="#repeat-decorators">Repeat Decorators</a></h2>
<h3 id="infinite-repeat-repeat"><a class="header" href="#infinite-repeat-repeat">Infinite Repeat: <code>repeat</code></a></h3>
<p>Loops the child infinitely. The child is re-executed immediately after completing (success or failure).</p>
<pre><code class="language-storybook">behavior InfinitePatrol {
repeat {
PatrolRoute
}
}
</code></pre>
<p><strong>Execution:</strong></p>
<ol>
<li>Run child</li>
<li>Child completes (success or failure)</li>
<li>Immediately run child again</li>
<li>Go to step 2 (forever)</li>
</ol>
<p><strong>Use cases:</strong></p>
<ul>
<li>Perpetual patrols</li>
<li>Endless background processes</li>
<li>Continuous monitoring</li>
</ul>
<p><strong>Warning:</strong> Infinite loops never return to parent. Ensure theyre appropriate for your use case.</p>
<h3 id="repeat-n-times-repeat-n"><a class="header" href="#repeat-n-times-repeat-n">Repeat N Times: <code>repeat N</code></a></h3>
<p>Repeats the child exactly N times, then returns success.</p>
<pre><code class="language-storybook">behavior CheckThreeTimes {
repeat(3) {
CheckDoor
}
}
</code></pre>
<p><strong>Execution:</strong></p>
<ol>
<li>Counter = 0</li>
<li>Run child</li>
<li>Counter++</li>
<li>If counter &lt; N, go to step 2</li>
<li>Return success</li>
</ol>
<p><strong>Use cases:</strong></p>
<ul>
<li>Fixed iteration counts</li>
<li>“Try three times then give up”</li>
<li>Deterministic looping</li>
</ul>
<h3 id="repeat-range-repeat-minmax"><a class="header" href="#repeat-range-repeat-minmax">Repeat Range: <code>repeat min..max</code></a></h3>
<p>Repeats the child between min and max times. At runtime, a specific count is selected within the range.</p>
<pre><code class="language-storybook">behavior SearchRandomly {
repeat(2..5) {
SearchArea
}
}
</code></pre>
<p><strong>Execution:</strong></p>
<ol>
<li>Select count C randomly from [min, max]</li>
<li>Repeat child C times (as in <code>repeat N</code>)</li>
</ol>
<p><strong>Use cases:</strong></p>
<ul>
<li>Variable behavior</li>
<li>Procedural variation</li>
<li>Non-deterministic actions</li>
</ul>
<p><strong>Validation:</strong></p>
<ul>
<li>min ≥ 0</li>
<li>max ≥ min</li>
<li>Both must be integers</li>
</ul>
<h2 id="invert-decorator"><a class="header" href="#invert-decorator">Invert Decorator</a></h2>
<p>Inverts the childs return value: success becomes failure, failure becomes success.</p>
<pre><code class="language-storybook">behavior AvoidEnemies {
invert {
IsEnemyNearby // Success if NOT nearby
}
}
</code></pre>
<p><strong>Truth table:</strong></p>
<div class="table-wrapper"><table><thead><tr><th>Child returns</th><th>Decorator returns</th></tr></thead><tbody>
<tr><td>Success</td><td>Failure</td></tr>
<tr><td>Failure</td><td>Success</td></tr>
<tr><td>Running</td><td>Running (unchanged)</td></tr>
</tbody></table>
</div>
<p><strong>Use cases:</strong></p>
<ul>
<li>Negating conditions (“if NOT X”)</li>
<li>Inverting success criteria</li>
<li>Converting “found” to “not found”</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="language-storybook">behavior SafeExploration {
choose safe_actions {
// Only explore if NOT dangerous
then explore {
invert { IsDangerous }
ExploreArea
}
// Default: Stay put
Wait
}
}
</code></pre>
<h2 id="retry-decorator"><a class="header" href="#retry-decorator">Retry Decorator</a></h2>
<p>Retries the child up to N times on failure. Returns success if any attempt succeeds, failure if all N attempts fail.</p>
<pre><code class="language-storybook">behavior PersistentDoor {
retry(5) {
OpenLockedDoor
}
}
</code></pre>
<p><strong>Execution:</strong></p>
<ol>
<li>Attempts = 0</li>
<li>Run child</li>
<li>If child succeeds → return success</li>
<li>If child fails:
<ul>
<li>Attempts++</li>
<li>If attempts &lt; N, go to step 2</li>
<li>Else return failure</li>
</ul>
</li>
</ol>
<p><strong>Use cases:</strong></p>
<ul>
<li>Unreliable actions (lockpicking, persuasion)</li>
<li>Network/resource operations</li>
<li>Probabilistic success</li>
</ul>
<p><strong>Example with context:</strong></p>
<pre><code class="language-storybook">behavior Thief_PickLock {
then attempt_entry {
// Try to pick lock (may fail)
retry(3) {
PickLock
}
// If succeeded, enter
EnterBuilding
// If failed after 3 tries, give up
}
}
</code></pre>
<p><strong>Validation:</strong></p>
<ul>
<li>N must be ≥ 1</li>
</ul>
<h2 id="timeout-decorator"><a class="header" href="#timeout-decorator">Timeout Decorator</a></h2>
<p>Fails the child if it doesnt complete within the specified duration.</p>
<pre><code class="language-storybook">behavior TimeLimitedPuzzle {
timeout(30s) {
SolvePuzzle
}
}
</code></pre>
<p><strong>Execution:</strong></p>
<ol>
<li>Start timer</li>
<li>Run child each tick</li>
<li>If child completes before timeout → return childs result</li>
<li>If timer expires → return failure (interrupt child)</li>
</ol>
<p><strong>Use cases:</strong></p>
<ul>
<li>Time-limited actions</li>
<li>Preventing infinite loops</li>
<li>Enforcing deadlines</li>
</ul>
<p><strong>Duration formats:</strong></p>
<ul>
<li><code>5s</code> - 5 seconds</li>
<li><code>10m</code> - 10 minutes</li>
<li><code>2h</code> - 2 hours</li>
<li><code>3d</code> - 3 days</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="language-storybook">behavior QuickDecision {
choose timed_choice {
// Give AI 5 seconds to find optimal move
timeout(5s) {
CalculateOptimalStrategy
}
// Fallback: Use simple heuristic
UseQuickHeuristic
}
}
</code></pre>
<p><strong>Notes:</strong></p>
<ul>
<li>Timer starts when decorator is first entered</li>
<li>Timer resets if decorator exits and re-enters</li>
<li>Child node should handle interruption gracefully</li>
</ul>
<h2 id="cooldown-decorator"><a class="header" href="#cooldown-decorator">Cooldown Decorator</a></h2>
<p>Prevents the child from running more than once per cooldown period. Fails immediately if called within cooldown.</p>
<pre><code class="language-storybook">behavior SpecialAbility {
cooldown(30s) {
FireCannon
}
}
</code></pre>
<p><strong>Execution:</strong></p>
<ol>
<li>Check last execution time</li>
<li>If (current_time - last_time) &lt; cooldown → return failure</li>
<li>Else:
<ul>
<li>Run child</li>
<li>Record current_time as last_time</li>
<li>Return childs result</li>
</ul>
</li>
</ol>
<p><strong>Use cases:</strong></p>
<ul>
<li>Rate-limiting abilities</li>
<li>Resource cooldowns (spells, items)</li>
<li>Preventing spam</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="language-storybook">behavior Mage_SpellCasting {
choose spells {
// Fireball: 10 second cooldown
cooldown(10s) {
CastFireball
}
// Lightning: 5 second cooldown
cooldown(5s) {
CastLightning
}
// Basic attack: No cooldown
MeleeAttack
}
}
</code></pre>
<p><strong>State management:</strong></p>
<ul>
<li>Cooldown state persists across behavior tree ticks</li>
<li>Each cooldown decorator instance has independent state</li>
<li>Cooldown timers are per-entity (not global)</li>
</ul>
<h2 id="if-decorator"><a class="header" href="#if-decorator">If Decorator</a></h2>
<p>Only runs the child if the condition is true. Fails immediately if condition is false.</p>
<pre><code class="language-storybook">behavior ConditionalAttack {
if(health &gt; 50) {
AggressiveAttack
}
}
</code></pre>
<p><strong>Execution:</strong></p>
<ol>
<li>Evaluate condition expression</li>
<li>If true → run child and return its result</li>
<li>If false → return failure (do not run child)</li>
</ol>
<p><strong>Use cases:</strong></p>
<ul>
<li>Preconditions (“only if X”)</li>
<li>Resource checks (“only if have mana”)</li>
<li>Safety checks (“only if safe”)</li>
</ul>
<p><strong>Expression syntax:</strong>
See <a href="./17-expressions.html">Expression Language</a> for complete syntax.</p>
<p><strong>Examples:</strong></p>
<pre><code class="language-storybook">behavior GuardedActions {
choose options {
// Only attack if have weapon and enemy close
if(has_weapon and distance &lt; 10) {
Attack
}
// Only heal if health below 50%
if(health &lt; (max_health * 0.5)) {
Heal
}
// Only flee if outnumbered
if(enemy_count &gt; ally_count) {
Flee
}
}
}
</code></pre>
<p><strong>Comparison with bare <code>if</code> conditions:</strong></p>
<pre><code class="language-storybook">// Using bare 'if' condition (checks every tick, no body)
then approach_and_attack {
if(enemy_nearby)
Approach
Attack
}
// Using 'if' decorator with body (precondition check, fails fast)
if(enemy_nearby) {
then do_attack {
Approach
Attack
}
}
</code></pre>
<p>The <code>if</code> decorator with a body is more efficient for gating expensive subtrees.</p>
<h2 id="force-result-decorators"><a class="header" href="#force-result-decorators">Force Result Decorators</a></h2>
<h3 id="succeed_always"><a class="header" href="#succeed_always"><code>succeed_always</code></a></h3>
<p>Always returns success, regardless of childs actual result.</p>
<pre><code class="language-storybook">behavior TryOptionalTask {
succeed_always {
AttemptBonus // Even if fails, we don't care
}
}
</code></pre>
<p><strong>Use cases:</strong></p>
<ul>
<li>Optional tasks that shouldnt block progress</li>
<li>Logging/monitoring actions</li>
<li>Best-effort operations</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="language-storybook">behavior QuestSequence {
then main_quest {
TalkToNPC
// Try to find secret, but don't fail quest if not found
succeed_always {
SearchForSecretDoor
}
ReturnToQuestGiver
}
}
</code></pre>
<h3 id="fail_always"><a class="header" href="#fail_always"><code>fail_always</code></a></h3>
<p>Always returns failure, regardless of childs actual result.</p>
<pre><code class="language-storybook">behavior TestFailure {
fail_always {
AlwaysSucceedsAction // But we force it to fail
}
}
</code></pre>
<p><strong>Use cases:</strong></p>
<ul>
<li>Testing/debugging</li>
<li>Forcing alternative paths</li>
<li>Disabling branches temporarily</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="language-storybook">behavior UnderConstruction {
choose abilities {
// Temporarily disabled feature
fail_always {
NewExperimentalAbility
}
// Fallback to old ability
ClassicAbility
}
}
</code></pre>
<h2 id="combining-decorators"><a class="header" href="#combining-decorators">Combining Decorators</a></h2>
<p>Decorators can nest to create complex behaviors:</p>
<pre><code class="language-storybook">behavior ComplexPattern {
// Repeat 3 times, each with 10 second timeout
repeat(3) {
timeout(10s) {
SolveSubproblem
}
}
}
</code></pre>
<p><strong>More complex nesting:</strong></p>
<pre><code class="language-storybook">behavior ResilientAction {
// If: Only if health &gt; 30
if(health &gt; 30) {
// Timeout: Must complete in 20 seconds
timeout(20s) {
// Retry: Try up to 5 times
retry(5) {
// Cooldown: Can only run once per minute
cooldown(1m) {
PerformComplexAction
}
}
}
}
}
</code></pre>
<p><strong>Execution order:</strong> Outside → Inside</p>
<ol>
<li>If checks condition</li>
<li>Timeout starts timer</li>
<li>Retry begins first attempt</li>
<li>Cooldown checks last execution time</li>
<li>Child action runs</li>
</ol>
<h2 id="duration-syntax"><a class="header" href="#duration-syntax">Duration Syntax</a></h2>
<p>Timeout and cooldown decorators use duration literals:</p>
<pre><code class="language-bnf">&lt;duration-literal&gt; ::= &lt;number&gt; &lt;unit&gt;
&lt;unit&gt; ::= "s" // seconds
| "m" // minutes
| "h" // hours
| "d" // days
&lt;number&gt; ::= &lt;digit&gt;+
</code></pre>
<p><strong>Examples:</strong></p>
<ul>
<li><code>5s</code> - 5 seconds</li>
<li><code>30s</code> - 30 seconds</li>
<li><code>2m</code> - 2 minutes</li>
<li><code>10m</code> - 10 minutes</li>
<li><code>1h</code> - 1 hour</li>
<li><code>24h</code> - 24 hours</li>
<li><code>7d</code> - 7 days</li>
</ul>
<p><strong>Validation:</strong></p>
<ul>
<li>Number must be positive integer</li>
<li>No compound durations (use <code>120s</code> not <code>2m</code> if runtime expects seconds)</li>
<li>No fractional units (<code>1.5m</code> not allowed; use <code>90s</code>)</li>
</ul>
<h2 id="comparison-table"><a class="header" href="#comparison-table">Comparison Table</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Decorator</th><th>Affects Success</th><th>Affects Failure</th><th>Affects Running</th><th>Stateful</th></tr></thead><tbody>
<tr><td><code>repeat</code></td><td>Repeat</td><td>Repeat</td><td>Wait</td><td>Yes (counter)</td></tr>
<tr><td><code>repeat N</code></td><td>Repeat</td><td>Repeat</td><td>Wait</td><td>Yes (counter)</td></tr>
<tr><td><code>repeat min..max</code></td><td>Repeat</td><td>Repeat</td><td>Wait</td><td>Yes (counter)</td></tr>
<tr><td><code>invert</code></td><td>→ Failure</td><td>→ Success</td><td>Unchanged</td><td>No</td></tr>
<tr><td><code>retry N</code></td><td>→ Success</td><td>Retry or fail</td><td>Wait</td><td>Yes (attempts)</td></tr>
<tr><td><code>timeout dur</code></td><td>→ Success</td><td>→ Success</td><td>→ Failure if expired</td><td>Yes (timer)</td></tr>
<tr><td><code>cooldown dur</code></td><td>→ Success</td><td>→ Success</td><td>→ Success</td><td>Yes (last time)</td></tr>
<tr><td><code>if(expr)</code></td><td>→ Success</td><td>→ Success</td><td>→ Success</td><td>No</td></tr>
<tr><td><code>succeed_always</code></td><td>→ Success</td><td>→ Success</td><td>→ Success</td><td>No</td></tr>
<tr><td><code>fail_always</code></td><td>→ Failure</td><td>→ Failure</td><td>→ Failure</td><td>No</td></tr>
</tbody></table>
</div>
<p><strong>Stateful decorators</strong> maintain state across ticks. <strong>Stateless decorators</strong> evaluate fresh every tick.</p>
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>Child required</strong>: All decorators must have exactly one child node</li>
<li><strong>Repeat count</strong>: <code>repeat N</code> requires N ≥ 0</li>
<li><strong>Repeat range</strong>: <code>repeat min..max</code> requires 0 ≤ min ≤ max</li>
<li><strong>Retry count</strong>: <code>retry N</code> requires N ≥ 1</li>
<li><strong>Duration positive</strong>: Timeout/cooldown durations must be &gt; 0</li>
<li><strong>Duration format</strong>: Must match <code>&lt;number&gt;&lt;unit&gt;</code> (e.g., <code>10s</code>, <code>5m</code>)</li>
<li><strong>Guard expression</strong>: Guard condition must be valid expression</li>
<li><strong>No empty decorators</strong>: <code>repeat { }</code> is invalid (missing child)</li>
</ol>
<h2 id="use-cases-by-category"><a class="header" href="#use-cases-by-category">Use Cases by Category</a></h2>
<h3 id="timing-control"><a class="header" href="#timing-control">Timing Control</a></h3>
<ul>
<li><strong>timeout</strong>: Prevent infinite loops, enforce time limits</li>
<li><strong>cooldown</strong>: Rate-limit abilities, prevent spam</li>
<li><strong>repeat</strong>: Continuous processes, patrols</li>
</ul>
<h3 id="reliability"><a class="header" href="#reliability">Reliability</a></h3>
<ul>
<li><strong>retry</strong>: Handle unreliable actions, probabilistic success</li>
<li><strong>if</strong>: Precondition checks, resource validation</li>
<li><strong>succeed_always</strong>: Optional tasks, best-effort</li>
</ul>
<h3 id="logic-control"><a class="header" href="#logic-control">Logic Control</a></h3>
<ul>
<li><strong>invert</strong>: Negate conditions, flip results</li>
<li><strong>fail_always</strong>: Disable branches, testing</li>
</ul>
<h3 id="iteration"><a class="header" href="#iteration">Iteration</a></h3>
<ul>
<li><strong>repeat N</strong>: Fixed loops, deterministic behavior</li>
<li><strong>repeat min..max</strong>: Variable loops, procedural variation</li>
</ul>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-use-guards-for-expensive-checks"><a class="header" href="#1-use-guards-for-expensive-checks">1. Use Guards for Expensive Checks</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">then expensive {
if(complex_condition)
ExpensiveOperation
}
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">if(complex_condition) {
ExpensiveOperation // Only runs if condition passes
}
</code></pre>
<h3 id="2-combine-timeout-with-retry"><a class="header" href="#2-combine-timeout-with-retry">2. Combine Timeout with Retry</a></h3>
<p><strong>Avoid:</strong> Infinite retry loops</p>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">timeout(30s) {
retry(5) {
UnreliableAction
}
}
</code></pre>
<h3 id="3-use-cooldowns-for-rate-limiting"><a class="header" href="#3-use-cooldowns-for-rate-limiting">3. Use Cooldowns for Rate Limiting</a></h3>
<p><strong>Avoid:</strong> Manual timing in actions</p>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">cooldown(10s) {
FireCannon
}
</code></pre>
<h3 id="4-invert-for-readable-conditions"><a class="header" href="#4-invert-for-readable-conditions">4. Invert for Readable Conditions</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">choose options {
then branch_a {
if(not is_dangerous)
Explore
}
}
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">choose options {
then branch_a {
invert { IsDangerous }
Explore
}
}
</code></pre>
<h3 id="5-succeed_always-for-optional-tasks"><a class="header" href="#5-succeed_always-for-optional-tasks">5. succeed_always for Optional Tasks</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">then quest {
MainTask
choose optional {
BonusTask
DoNothing // Awkward fallback
}
NextTask
}
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">then quest {
MainTask
succeed_always { BonusTask } // Try bonus, don't fail quest
NextTask
}
</code></pre>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./11-behavior-trees.html">Behavior Trees</a> - Using decorators in behavior trees</li>
<li><a href="./17-expressions.html">Expression Language</a> - Guard condition syntax</li>
<li><a href="./18-value-types.html">Value Types</a> - Duration literals</li>
<li><a href="../advanced/20-patterns.html">Design Patterns</a> - Common decorator patterns</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Composability</strong>: Decorators can nest for complex control flow</li>
<li><strong>Separation of concerns</strong>: Decorators handle control flow, children handle logic</li>
<li><strong>State management</strong>: Stateful decorators (repeat, retry, timeout, cooldown) persist across ticks</li>
<li><strong>Performance</strong>: Guards prevent unnecessary child execution</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/11-behavior-trees.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/13-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="../reference/11-behavior-trees.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/13-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,845 @@
<!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>Life arcs are state machines that model how characters or other entities evolve over time. They define discrete states and the conditions under which an entity transitions between states. Life arcs capture character development, quest progress, relationship phases, and any other finite-state processes.</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 is a finite state machine (FSM) composed of:</p>
<ul>
<li><strong>States</strong>: Discrete phases or modes (e.g., “happy”, “angry”, “sleeping”)</li>
<li><strong>Transitions</strong>: Conditional edges between states (e.g., “when health &lt; 20, go to fleeing”)</li>
<li><strong>On-enter actions</strong>: Field updates that occur when entering a state</li>
</ul>
<pre><code>[State A] --condition--&gt; [State B] --condition--&gt; [State C]
| | |
on enter on enter on enter
(set fields) (set fields) (set fields)
</code></pre>
<p>Life arcs run continuously, evaluating transitions every tick and moving between states as conditions become true.</p>
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;life-arc-decl&gt; ::= "life_arc" &lt;identifier&gt; &lt;body&gt;
&lt;body&gt; ::= "{" &lt;prose-blocks&gt;* &lt;state&gt;+ "}"
&lt;state&gt; ::= "state" &lt;identifier&gt; "{" &lt;state-body&gt;* "}"
&lt;state-body&gt; ::= &lt;on-enter-block&gt;
| &lt;transition&gt;
| &lt;prose-block&gt;
&lt;on-enter-block&gt; ::= "on" "enter" "{" &lt;field&gt;+ "}"
&lt;transition&gt; ::= "on" &lt;expression&gt; "-&gt;" &lt;identifier&gt;
&lt;prose-block&gt; ::= "---" &lt;identifier&gt; &lt;content&gt; "---"
</code></pre>
<h2 id="states"><a class="header" href="#states">States</a></h2>
<p>A state represents a discrete phase in an entitys lifecycle.</p>
<h3 id="basic-state"><a class="header" href="#basic-state">Basic State</a></h3>
<pre><code class="language-storybook">life_arc SimpleArc {
state idle {
---narrative
The character is doing nothing, waiting for something to happen.
---
}
state active {
---narrative
The character is engaged in their primary activity.
---
}
}
</code></pre>
<h3 id="state-names"><a class="header" href="#state-names">State Names</a></h3>
<p>State names are identifiers that must be unique within the life arc. Use descriptive names:</p>
<ul>
<li><strong>Good</strong>: <code>idle</code>, <code>combat</code>, <code>sleeping</code>, <code>enlightened</code></li>
<li><strong>Avoid</strong>: <code>state1</code>, <code>s</code>, <code>temp</code></li>
</ul>
<h2 id="on-enter-actions"><a class="header" href="#on-enter-actions">On-Enter Actions</a></h2>
<p>When entering a state, the <code>on enter</code> block updates entity fields.</p>
<h3 id="syntax-1"><a class="header" href="#syntax-1">Syntax</a></h3>
<pre><code class="language-storybook">state state_name {
on enter {
EntityName.field_name: value
EntityName.other_field: other_value
}
}
</code></pre>
<h3 id="examples"><a class="header" href="#examples">Examples</a></h3>
<pre><code class="language-storybook">life_arc CharacterMood {
state happy {
on enter {
Martha.emotional_state: "happy"
Martha.energy: 100
}
}
state tired {
on enter {
Martha.emotional_state: "exhausted"
Martha.energy: 20
}
}
}
</code></pre>
<h3 id="field-update-semantics"><a class="header" href="#field-update-semantics">Field Update Semantics</a></h3>
<ul>
<li><strong>Target</strong>: <code>EntityName.field_name</code> must reference an existing character/entity</li>
<li><strong>Value</strong>: Any valid <a href="./18-value-types.html">value type</a></li>
<li><strong>Effect</strong>: Field is set to the specified value when state is entered</li>
<li><strong>Timing</strong>: Happens immediately upon transition to the state</li>
</ul>
<h3 id="multiple-field-updates"><a class="header" href="#multiple-field-updates">Multiple Field Updates</a></h3>
<pre><code class="language-storybook">state exhausted_baker {
on enter {
Martha.energy: 0.1
Martha.mood: "stressed"
Martha.quality_output: 0.7
Martha.needs_break: true
}
---narrative
After a sixteen-hour shift during the harvest festival,
Martha's hands are shaking. She knows her bread quality is
suffering and reluctantly steps away from the oven.
---
}
</code></pre>
<h2 id="transitions"><a class="header" href="#transitions">Transitions</a></h2>
<p>Transitions define conditional edges between states. When a transitions condition becomes true, the state machine moves to the target state.</p>
<h3 id="syntax-2"><a class="header" href="#syntax-2">Syntax</a></h3>
<pre><code class="language-storybook">state source_state {
on condition_expression -&gt; target_state
on another_condition -&gt; another_target
}
</code></pre>
<h3 id="basic-transitions"><a class="header" href="#basic-transitions">Basic Transitions</a></h3>
<pre><code class="language-storybook">life_arc Combat {
state fighting {
on health &lt; 20 -&gt; fleeing
on enemy_defeated -&gt; victorious
}
state fleeing {
on safe_distance_reached -&gt; idle
}
state victorious {
on celebration_complete -&gt; idle
}
state idle {}
}
</code></pre>
<h3 id="expression-conditions"><a class="header" href="#expression-conditions">Expression Conditions</a></h3>
<p>Transitions use the full <a href="./17-expressions.html">expression language</a>:</p>
<p><strong>Comparisons:</strong></p>
<pre><code class="language-storybook">on health &lt; 20 -&gt; low_health
on distance &gt; 100 -&gt; far_away
on count == 0 -&gt; empty
</code></pre>
<p><strong>Boolean fields:</strong></p>
<pre><code class="language-storybook">on is_hostile -&gt; combat
on completed -&gt; done
on not ready -&gt; waiting
</code></pre>
<p><strong>Logical operators:</strong></p>
<pre><code class="language-storybook">on health &lt; 20 and not has_potion -&gt; desperate
on is_day or is_lit -&gt; visible
</code></pre>
<p><strong>Complex conditions:</strong></p>
<pre><code class="language-storybook">on (health &lt; 50 and enemy_count &gt; 3) or surrounded -&gt; retreat
on forall e in enemies: e.defeated -&gt; victory
</code></pre>
<h3 id="multiple-transitions"><a class="header" href="#multiple-transitions">Multiple Transitions</a></h3>
<p>A state can have multiple outgoing transitions:</p>
<pre><code class="language-storybook">state monitoring {
on status is "active" -&gt; active_state
on status is "inactive" -&gt; inactive_state
on status is "error" -&gt; error_state
on shutdown_requested -&gt; shutting_down
}
</code></pre>
<p><strong>Evaluation order:</strong></p>
<ul>
<li>Transitions are evaluated in declaration order (top to bottom)</li>
<li>First transition with a true condition is taken</li>
<li>If no conditions are true, state remains unchanged</li>
</ul>
<h3 id="self-transitions"><a class="header" href="#self-transitions">Self-Transitions</a></h3>
<p>A state can transition to itself:</p>
<pre><code class="language-storybook">state patrolling {
on enemy_spotted -&gt; combat
on checkpoint_reached -&gt; patrolling // Reset patrol state
}
</code></pre>
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
<h3 id="elenas-career-journey"><a class="header" href="#elenas-career-journey">Elenas Career Journey</a></h3>
<pre><code class="language-storybook">life_arc ElenaCareer {
---description
Tracks Elena's progression from nervous apprentice to confident
master baker. Each state represents a key phase of her career.
---
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; 30 -&gt; senior_journeyman
---narrative
Elena runs the morning shift alone while Martha handles
special orders. Customers start asking for "Elena's rolls."
She begins experimenting with her own recipes.
---
}
state senior_journeyman {
on enter {
Elena.skill_level: advanced
Elena.confidence: steady
}
on recipes_mastered &gt; 50 -&gt; master
---narrative
Elena develops her signature recipe: rosemary olive bread
that becomes the bakery's bestseller. She handles difficult
customers with grace and trains new helpers.
---
}
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>
<h3 id="quest-progress"><a class="header" href="#quest-progress">Quest Progress</a></h3>
<pre><code class="language-storybook">life_arc HeroQuest {
state not_started {
on talked_to_elder -&gt; received_quest
}
state received_quest {
on enter {
Hero.quest_active: true
Hero.quest_step: 0
}
on found_first_artifact -&gt; collecting
}
state collecting {
on enter {
Hero.quest_step: 1
}
on artifact_count == 3 -&gt; returning
on failed_trial -&gt; failed
}
state returning {
on enter {
Hero.quest_step: 2
}
on reached_elder -&gt; completed
}
state completed {
on enter {
Hero.quest_active: false
Hero.quest_completed: true
Hero.reputation: 100
}
---narrative
The hero returns triumphant, artifacts in hand. The elder
bestows great rewards and the village celebrates.
---
}
state failed {
on enter {
Hero.quest_active: false
Hero.quest_failed: true
}
on retry_accepted -&gt; received_quest
}
}
</code></pre>
<h3 id="name-checker-equality-examples"><a class="header" href="#name-checker-equality-examples">Name Checker (Equality Examples)</a></h3>
<pre><code class="language-storybook">life_arc NameCheck {
state checking {
on name is "Martha" -&gt; found_martha
on name is "Jane" -&gt; found_jane
}
state found_martha {
on ready -&gt; checking
}
state found_jane {
on ready -&gt; checking
}
}
</code></pre>
<h3 id="status-monitor"><a class="header" href="#status-monitor">Status Monitor</a></h3>
<pre><code class="language-storybook">life_arc StatusMonitor {
state monitoring {
on status is active -&gt; active_state
on status is inactive -&gt; inactive_state
on status is error -&gt; error_state
}
state active_state {
on enter {
System.led_color: "green"
}
on status is inactive -&gt; inactive_state
on status is error -&gt; error_state
}
state inactive_state {
on enter {
System.led_color: "yellow"
}
on status is active -&gt; active_state
on status is error -&gt; error_state
}
state error_state {
on enter {
System.led_color: "red"
System.alarm: true
}
on error_cleared -&gt; monitoring
}
}
</code></pre>
<h3 id="character-mood-swings"><a class="header" href="#character-mood-swings">Character Mood Swings</a></h3>
<pre><code class="language-storybook">life_arc MoodSwings {
state neutral {
on provoked -&gt; angry
on complimented -&gt; happy
on tired -&gt; sleepy
}
state angry {
on enter {
Character.aggression: 0.9
Character.willingness_to_talk: 0.1
}
on calmed_down -&gt; neutral
on escalated -&gt; furious
}
state furious {
on enter {
Character.aggression: 1.0
Character.will_attack: true
}
on timeout_elapsed -&gt; angry
on apologized_to -&gt; neutral
}
state happy {
on enter {
Character.aggression: 0.0
Character.willingness_to_talk: 1.0
Character.gives_discounts: true
}
on insulted -&gt; neutral
on bored -&gt; neutral
}
state sleepy {
on enter {
Character.responsiveness: 0.2
}
on woke_up -&gt; neutral
on fell_asleep -&gt; sleeping
}
state sleeping {
on enter {
Character.responsiveness: 0.0
Character.is_vulnerable: true
}
on woke_up -&gt; neutral
}
}
</code></pre>
<h2 id="execution-semantics"><a class="header" href="#execution-semantics">Execution Semantics</a></h2>
<h3 id="tick-based-evaluation"><a class="header" href="#tick-based-evaluation">Tick-Based Evaluation</a></h3>
<p>Life arcs evaluate transitions every “tick” (typically once per frame):</p>
<ol>
<li><strong>Current state</strong>: Start in the current state</li>
<li><strong>Evaluate transitions</strong>: Check each outgoing transitions condition (in order)</li>
<li><strong>First true transition</strong>: Take the first transition with a true condition</li>
<li><strong>Enter new state</strong>: Execute the new states <code>on enter</code> block</li>
<li><strong>Repeat next tick</strong></li>
</ol>
<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> is taken:</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> transition fires (even though <code>defensive</code> condition is also true).</p>
<h3 id="state-persistence"><a class="header" href="#state-persistence">State Persistence</a></h3>
<p>The current state persists across ticks until a transition fires.</p>
<pre><code class="language-storybook">state waiting {
on signal_received -&gt; active
}
</code></pre>
<p>The entity remains in <code>waiting</code> state indefinitely until <code>signal_received</code> becomes true.</p>
<h3 id="on-enter-execution"><a class="header" href="#on-enter-execution">On-Enter Execution</a></h3>
<p><code>on enter</code> blocks execute <strong>once</strong> when entering the state, not every tick:</p>
<pre><code class="language-storybook">state combat {
on enter {
Character.weapon_drawn: true // Runs once when entering combat
}
}
</code></pre>
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>At least one state</strong>: Life arc must contain at least one state</li>
<li><strong>Unique state names</strong>: State names must be unique within the life arc</li>
<li><strong>Valid transitions</strong>: Transition targets must reference defined states</li>
<li><strong>No orphan states</strong>: All states should be reachable (warning, not error)</li>
<li><strong>Expression validity</strong>: Transition conditions must be well-formed expressions</li>
<li><strong>Field targets</strong>: On-enter field updates must reference valid entities/fields</li>
<li><strong>No cyclic immediate transitions</strong>: Avoid transitions that fire immediately in a loop</li>
</ol>
<h2 id="design-patterns"><a class="header" href="#design-patterns">Design Patterns</a></h2>
<h3 id="1-hub-and-spoke"><a class="header" href="#1-hub-and-spoke">1. Hub-and-Spoke</a></h3>
<p>A central “hub” state with transitions to specialized states:</p>
<pre><code class="language-storybook">life_arc HubPattern {
state idle {
on combat_triggered -&gt; combat
on quest_accepted -&gt; questing
on entered_shop -&gt; shopping
}
state combat {
on combat_ended -&gt; idle
}
state questing {
on quest_completed -&gt; idle
}
state shopping {
on left_shop -&gt; idle
}
}
</code></pre>
<h3 id="2-linear-progression"><a class="header" href="#2-linear-progression">2. Linear Progression</a></h3>
<p>States form a linear 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="3-cyclic-states"><a class="header" href="#3-cyclic-states">3. Cyclic States</a></h3>
<p>States form a cycle (day/night, seasons):</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>
<h3 id="4-hierarchical-simulated"><a class="header" href="#4-hierarchical-simulated">4. Hierarchical (Simulated)</a></h3>
<p>Use multiple life arcs for hierarchical state:</p>
<pre><code class="language-storybook">// Top-level life arc
life_arc CharacterState {
state alive {
on health &lt;= 0 -&gt; dead
}
state dead {}
}
// Nested life arc (only active when alive)
life_arc CombatState {
state idle {
on enemy_nearby -&gt; combat
}
state combat {
on enemy_defeated -&gt; idle
}
}
</code></pre>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-use-descriptive-state-names"><a class="header" href="#1-use-descriptive-state-names">1. Use Descriptive State Names</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">state s1 { ... }
state s2 { ... }
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">state waiting_for_player { ... }
state engaged_in_combat { ... }
</code></pre>
<h3 id="2-add-narrative-prose-blocks"><a class="header" href="#2-add-narrative-prose-blocks">2. Add Narrative Prose Blocks</a></h3>
<pre><code class="language-storybook">state master_baker {
---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>
<h3 id="3-order-transitions-by-priority"><a class="header" href="#3-order-transitions-by-priority">3. Order Transitions by Priority</a></h3>
<pre><code class="language-storybook">state health_check {
on health &lt;= 0 -&gt; dead // Most urgent
on health &lt; 20 -&gt; critical // Very urgent
on health &lt; 50 -&gt; wounded // Moderate
}
</code></pre>
<h3 id="4-avoid-orphan-states"><a class="header" href="#4-avoid-orphan-states">4. Avoid Orphan States</a></h3>
<p>Ensure all states are reachable from the initial state.</p>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">life_arc Broken {
state start {
on ready -&gt; middle
}
state middle {
on done -&gt; end
}
state unreachable {} // No transition leads here!
state end {}
}
</code></pre>
<h3 id="5-use-on-enter-for-state-initialization"><a class="header" href="#5-use-on-enter-for-state-initialization">5. Use On-Enter for State Initialization</a></h3>
<pre><code class="language-storybook">state combat {
on enter {
Character.weapon_drawn: true
Character.combat_stance: "aggressive"
Character.target: nearest_enemy
}
}
</code></pre>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./10-characters.html">Characters</a> - Characters can have associated life arcs</li>
<li><a href="./17-expressions.html">Expression Language</a> - Transition condition syntax</li>
<li><a href="./18-value-types.html">Value Types</a> - On-enter field value types</li>
<li><a href="./19-validation.html">Validation Rules</a> - Life arc validation constraints</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Finite State Machines (FSM)</strong>: Life arcs are FSMs</li>
<li><strong>Character development</strong>: Track character growth over time</li>
<li><strong>Quest states</strong>: Model quest progression</li>
<li><strong>Mood systems</strong>: Model emotional states</li>
<li><strong>Lifecycle modeling</strong>: Birth, growth, aging, death</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/12-decorators.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/14-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="../reference/12-decorators.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/14-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,865 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Schedules - 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"><a class="header" href="#schedules">Schedules</a></h1>
<p>Schedules define time-based routines for characters and institutions. They specify what activities occur during specific time ranges, support seasonal variations, recurring events, and template composition. Schedules enable rich temporal behavior from simple daily routines to complex year-long patterns.</p>
<h2 id="what-is-a-schedule"><a class="header" href="#what-is-a-schedule">What is a Schedule?</a></h2>
<p>A schedule is a collection of time blocks that define activities throughout a day, week, season, or year:</p>
<ul>
<li><strong>Blocks</strong>: Time ranges (e.g., <code>06:00 - 14:00</code>) with associated activities/behaviors</li>
<li><strong>Temporal constraints</strong>: When blocks apply (season, day of week, month)</li>
<li><strong>Recurrence patterns</strong>: Repeating events (e.g., “Market Day every Earthday”)</li>
<li><strong>Composition</strong>: Schedules can extend and override other schedules</li>
</ul>
<pre><code>Schedule: BakerySchedule
├─ Block: 06:00 - 08:00 → prepare_dough
├─ Block: 08:00 - 14:00 → serve_customers
├─ Recurrence: Market Day (on Earthday) → special_market_hours
└─ Extends: BaseBusiness (inherits closing hours)
</code></pre>
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;schedule-decl&gt; ::= "schedule" &lt;identifier&gt; &lt;extends-clause&gt;? &lt;body&gt;
&lt;extends-clause&gt; ::= "extends" &lt;identifier&gt;
&lt;body&gt; ::= "{" &lt;schedule-item&gt;* "}"
&lt;schedule-item&gt; ::= &lt;schedule-block&gt;
| &lt;recurrence-pattern&gt;
&lt;schedule-block&gt; ::= "block" &lt;block-name&gt;? "{" &lt;block-content&gt;+ "}"
&lt;block-name&gt; ::= &lt;identifier&gt;
&lt;block-content&gt; ::= &lt;time-range&gt;
| "action" ":" &lt;qualified-path&gt;
| &lt;temporal-constraint&gt;
| &lt;field&gt;
&lt;time-range&gt; ::= &lt;time&gt; "-" &lt;time&gt;
&lt;temporal-constraint&gt; ::= "on" &lt;temporal-spec&gt;
&lt;temporal-spec&gt; ::= "season" &lt;identifier&gt;
| "day" &lt;identifier&gt;
| "month" &lt;identifier&gt;
| "dates" &lt;string&gt; ".." &lt;string&gt;
&lt;recurrence-pattern&gt; ::= "recurs" &lt;identifier&gt; &lt;temporal-constraint&gt; "{" &lt;schedule-block&gt;+ "}"
</code></pre>
<h2 id="schedule-blocks"><a class="header" href="#schedule-blocks">Schedule Blocks</a></h2>
<p>A schedule block defines an activity during a specific time range.</p>
<h3 id="basic-block"><a class="header" href="#basic-block">Basic Block</a></h3>
<pre><code class="language-storybook">schedule SimpleBaker {
block {
06:00 - 14:00
action: baking::prepare_and_sell
}
}
</code></pre>
<h3 id="named-block"><a class="header" href="#named-block">Named Block</a></h3>
<p>Named blocks support the override system:</p>
<pre><code class="language-storybook">schedule BakeryBase {
block work {
09:00 - 17:00
action: baking::standard_work
}
}
schedule EarlyBaker extends BakeryBase {
block work { // Override by name
05:00 - 13:00
action: baking::early_shift
}
}
</code></pre>
<h3 id="block-content"><a class="header" href="#block-content">Block Content</a></h3>
<p>Blocks can contain:</p>
<ol>
<li><strong>Time range</strong> (required): <code>HH:MM - HH:MM</code></li>
<li><strong>Action</strong> (optional): Behavior tree reference</li>
<li><strong>Temporal constraint</strong> (optional): When the block applies</li>
<li><strong>Fields</strong> (optional): Additional metadata</li>
</ol>
<pre><code class="language-storybook">schedule CompleteBlock {
block morning_work {
06:00 - 12:00
action: work::morning_routine
on season summer
location: "Outdoor Market"
}
}
</code></pre>
<h2 id="time-ranges"><a class="header" href="#time-ranges">Time Ranges</a></h2>
<p>Time ranges use 24-hour clock format.</p>
<h3 id="syntax-1"><a class="header" href="#syntax-1">Syntax</a></h3>
<pre><code class="language-storybook">HH:MM - HH:MM
</code></pre>
<ul>
<li><strong>HH</strong>: Hour (00-23)</li>
<li><strong>MM</strong>: Minute (00-59)</li>
<li>Optional seconds: <code>HH:MM:SS - HH:MM:SS</code></li>
</ul>
<h3 id="examples"><a class="header" href="#examples">Examples</a></h3>
<pre><code class="language-storybook">schedule Examples {
block early { 05:00 - 08:00, action: prep }
block midday { 12:00 - 13:00, action: lunch }
block late { 20:00 - 23:59, action: closing }
}
</code></pre>
<h3 id="overnight-ranges"><a class="header" href="#overnight-ranges">Overnight Ranges</a></h3>
<p>Blocks can span midnight:</p>
<pre><code class="language-storybook">schedule NightShift {
block night_work {
22:00 - 06:00 // 10 PM to 6 AM next day
action: security::patrol
}
}
</code></pre>
<p>The system interprets this as 22:00-24:00 (day 1) + 00:00-06:00 (day 2).</p>
<h2 id="actions"><a class="header" href="#actions">Actions</a></h2>
<p>The <code>action</code> field links a schedule block to a behavior tree.</p>
<h3 id="syntax-2"><a class="header" href="#syntax-2">Syntax</a></h3>
<pre><code class="language-storybook">action: &lt;qualified-path&gt;
</code></pre>
<h3 id="examples-1"><a class="header" href="#examples-1">Examples</a></h3>
<pre><code class="language-storybook">schedule BakerSchedule {
block morning {
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>
<h3 id="qualified-paths"><a class="header" href="#qualified-paths">Qualified Paths</a></h3>
<p>Actions can reference behaviors from other modules:</p>
<pre><code class="language-storybook">schedule GuardSchedule {
block patrol {
08:00 - 16:00
action: behaviors::guards::patrol_route
}
block training {
16:00 - 18:00
action: combat::training::drills
}
}
</code></pre>
<h2 id="temporal-constraints"><a class="header" href="#temporal-constraints">Temporal Constraints</a></h2>
<p>Temporal constraints specify when a block applies (season, day of week, month, date range).</p>
<h3 id="season-constraint"><a class="header" href="#season-constraint">Season Constraint</a></h3>
<pre><code class="language-storybook">schedule SeasonalHours {
block summer_hours {
06:00 - 20:00
action: work::long_day
on season summer
}
block winter_hours {
07:00 - 18:00
action: work::short_day
on season winter
}
}
</code></pre>
<p><strong>Season values</strong> are defined by enums in your storybook:</p>
<pre><code class="language-storybook">enum Season {
spring
summer
fall
winter
}
</code></pre>
<h3 id="day-of-week-constraint"><a class="header" href="#day-of-week-constraint">Day of Week Constraint</a></h3>
<pre><code class="language-storybook">schedule WeeklyPattern {
block weekday_work {
09:00 - 17:00
action: work::standard
on day monday // Also: tuesday, wednesday, thursday, friday, saturday, sunday
}
block weekend_rest {
10:00 - 16:00
action: leisure::relax
on day saturday
}
}
</code></pre>
<p><strong>Day values</strong> are typically defined by a <code>DayOfWeek</code> enum:</p>
<pre><code class="language-storybook">enum DayOfWeek {
monday
tuesday
wednesday
thursday
friday
saturday
sunday
}
</code></pre>
<h3 id="month-constraint"><a class="header" href="#month-constraint">Month Constraint</a></h3>
<pre><code class="language-storybook">schedule AnnualPattern {
block harvest {
06:00 - 20:00
action: farming::harvest_crops
on month october
}
block spring_planting {
07:00 - 19:00
action: farming::plant_seeds
on month april
}
}
</code></pre>
<h3 id="date-range-constraint"><a class="header" href="#date-range-constraint">Date Range Constraint</a></h3>
<pre><code class="language-storybook">schedule TouristSeason {
block high_season {
08:00 - 22:00
action: tourism::busy_service
on dates "Jun 1" .. "Sep 1"
}
}
</code></pre>
<p><strong>Note</strong>: Date format is implementation-specific. Check your runtime for supported formats.</p>
<h2 id="recurrence-patterns"><a class="header" href="#recurrence-patterns">Recurrence Patterns</a></h2>
<p>Recurrence patterns define recurring events that modify the schedule.</p>
<h3 id="syntax-3"><a class="header" href="#syntax-3">Syntax</a></h3>
<pre><code class="language-storybook">recurs EventName on &lt;temporal-constraint&gt; {
&lt;schedule-block&gt;+
}
</code></pre>
<h3 id="examples-2"><a class="header" href="#examples-2">Examples</a></h3>
<h4 id="weekly-recurring-event"><a class="header" href="#weekly-recurring-event">Weekly Recurring Event</a></h4>
<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>
<h4 id="monthly-recurring-event"><a class="header" href="#monthly-recurring-event">Monthly Recurring Event</a></h4>
<pre><code class="language-storybook">schedule TownSchedule {
recurs CouncilMeeting on month first_monday {
block meeting {
18:00 - 21:00
action: governance::council_session
}
}
}
</code></pre>
<h4 id="seasonal-recurring-event"><a class="header" href="#seasonal-recurring-event">Seasonal Recurring Event</a></h4>
<pre><code class="language-storybook">schedule FarmSchedule {
recurs SpringPlanting on season spring {
block planting {
06:00 - 18:00
action: farming::plant_all_fields
}
}
recurs FallHarvest on season fall {
block harvest {
06:00 - 20:00
action: farming::harvest_all_crops
}
}
}
</code></pre>
<h2 id="schedule-composition"><a class="header" href="#schedule-composition">Schedule Composition</a></h2>
<p>Schedules can extend other schedules using <code>extends</code>, inheriting and overriding blocks.</p>
<h3 id="extends-clause"><a class="header" href="#extends-clause">Extends Clause</a></h3>
<pre><code class="language-storybook">schedule Base {
block work {
09:00 - 17:00
action: work::standard
}
}
schedule Extended extends Base {
block work { // Override inherited block
05:00 - 13:00
action: work::early_shift
}
}
</code></pre>
<h3 id="override-semantics"><a class="header" href="#override-semantics">Override Semantics</a></h3>
<p>When extending a schedule:</p>
<ol>
<li><strong>Named blocks</strong> with matching names override base blocks</li>
<li><strong>Unnamed blocks</strong> are appended</li>
<li><strong>Time range</strong> can change</li>
<li><strong>Action</strong> can change</li>
<li><strong>Constraints</strong> can be added/changed</li>
</ol>
<h3 id="multiple-levels"><a class="header" href="#multiple-levels">Multiple Levels</a></h3>
<pre><code class="language-storybook">schedule BaseWork {
block work {
09:00 - 17:00
action: work::standard
}
}
schedule BakerWork extends BaseWork {
block work {
05:00 - 13:00 // Earlier hours
action: baking::work
}
}
schedule MasterBaker extends BakerWork {
block work {
03:00 - 11:00 // Even earlier!
action: baking::master_work
}
block teaching {
14:00 - 16:00
action: baking::teach_apprentice
}
}
</code></pre>
<p><strong>Resolution:</strong></p>
<ul>
<li><code>work</code> block: 03:00-11:00 with <code>baking::master_work</code> (MasterBaker overrides BakerWork overrides BaseWork)</li>
<li><code>teaching</code> block: 14:00-16:00 with <code>baking::teach_apprentice</code> (from MasterBaker)</li>
</ul>
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
<h3 id="simple-daily-schedule"><a class="header" href="#simple-daily-schedule">Simple Daily Schedule</a></h3>
<pre><code class="language-storybook">schedule SimpleFarmer {
block sleep {
22:00 - 05:00
action: rest::sleep
}
block morning_chores {
05:00 - 08:00
action: farming::feed_animals
}
block fieldwork {
08:00 - 17:00
action: farming::tend_crops
}
block evening_chores {
17:00 - 19:00
action: farming::evening_tasks
}
block leisure {
19:00 - 22:00
action: social::family_time
}
}
</code></pre>
<h3 id="seasonal-variation"><a class="header" href="#seasonal-variation">Seasonal Variation</a></h3>
<pre><code class="language-storybook">schedule SeasonalBaker {
block summer_work {
06:00 - 20:00
action: baking::long_shift
on season summer
}
block winter_work {
07:00 - 18:00
action: baking::short_shift
on season winter
}
block spring_work {
06:30 - 19:00
action: baking::medium_shift
on season spring
}
block fall_work {
06:30 - 19:00
action: baking::medium_shift
on season fall
}
}
</code></pre>
<h3 id="weekly-pattern-with-recurrence"><a class="header" href="#weekly-pattern-with-recurrence">Weekly Pattern with Recurrence</a></h3>
<pre><code class="language-storybook">schedule InnkeeperSchedule {
// Weekday routine
block weekday_service {
08:00 - 22:00
action: inn::regular_service
on day monday // Repeat for each weekday
}
block weekday_service {
08:00 - 22:00
action: inn::regular_service
on day tuesday
}
// ... (wednesday, thursday, friday)
// Weekend hours
block weekend_service {
10:00 - 24:00
action: inn::busy_service
on day saturday
}
block weekend_service {
10:00 - 24:00
action: inn::busy_service
on day sunday
}
// Weekly market day
recurs MarketDay on day wednesday {
block market_prep {
06:00 - 08:00
action: inn::prepare_market_goods
}
block market_rush {
08:00 - 16:00
action: inn::market_day_chaos
}
}
}
</code></pre>
<h3 id="extended-schedule"><a class="header" href="#extended-schedule">Extended Schedule</a></h3>
<pre><code class="language-storybook">schedule BaseShopkeeper {
block open {
09:00 - 17:00
action: shop::standard_hours
}
}
schedule BlacksmithSchedule extends BaseShopkeeper {
block open { // Override
06:00 - 18:00 // Longer hours
action: smithing::work
}
block forge_heat { // New block
05:00 - 06:00
action: smithing::heat_forge
}
}
</code></pre>
<h3 id="complex-year-round-schedule"><a class="header" href="#complex-year-round-schedule">Complex Year-Round Schedule</a></h3>
<pre><code class="language-storybook">schedule MasterBakerYear {
// Daily base pattern
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
}
// Seasonal variations
block summer_hours {
10:00 - 20:00 // Extended sales
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 events
recurs HarvestFestival on dates "Sep 20" .. "Sep 25" {
block festival {
06:00 - 23:00
action: baking::festival_mode
}
}
}
</code></pre>
<h2 id="integration-with-characters"><a class="header" href="#integration-with-characters">Integration with Characters</a></h2>
<p>Characters link to schedules using the <code>uses schedule</code> clause:</p>
<pre><code class="language-storybook">character Martha {
uses schedule: BakerySchedule
}
</code></pre>
<p><strong>Multiple schedules:</strong></p>
<pre><code class="language-storybook">character Innkeeper {
uses schedules: [WeekdaySchedule, WeekendSchedule]
}
</code></pre>
<p>See <a href="./10-characters.html#schedule-integration">Characters</a> for complete integration syntax.</p>
<h2 id="execution-semantics"><a class="header" href="#execution-semantics">Execution Semantics</a></h2>
<h3 id="time-based-selection"><a class="header" href="#time-based-selection">Time-Based Selection</a></h3>
<p>At any given time, the runtime:</p>
<ol>
<li><strong>Evaluates temporal constraints</strong>: Which blocks apply right now?</li>
<li><strong>Selects matching block</strong>: First block whose time range and constraint match</li>
<li><strong>Executes action</strong>: Runs the associated behavior tree</li>
<li><strong>Repeats next tick</strong></li>
</ol>
<h3 id="priority-order"><a class="header" href="#priority-order">Priority Order</a></h3>
<p>When multiple blocks could apply:</p>
<ol>
<li><strong>Recurrences</strong> take priority over regular blocks</li>
<li><strong>Constraints</strong> filter blocks (season, day, month)</li>
<li><strong>Time ranges</strong> must overlap current time</li>
<li><strong>First match</strong> wins (declaration order)</li>
</ol>
<h3 id="block-overlap"><a class="header" href="#block-overlap">Block Overlap</a></h3>
<p>Blocks can overlap in time. The runtime selects the first matching block.</p>
<p><strong>Example:</strong></p>
<pre><code class="language-storybook">schedule Overlapping {
block general {
08:00 - 17:00
action: work::general
}
block specialized {
10:00 - 12:00 // Overlaps with general
action: work::specialized
on day wednesday
}
}
</code></pre>
<p>On Wednesday at 11:00, <code>specialized</code> block is selected (more specific constraint).</p>
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>Time format</strong>: Times must be valid HH:MM or HH:MM:SS (24-hour)</li>
<li><strong>Time order</strong>: Start time must be before end time (unless spanning midnight)</li>
<li><strong>Extends exists</strong>: If using <code>extends</code>, base schedule must be defined</li>
<li><strong>No circular extends</strong>: Cannot form circular dependency chains</li>
<li><strong>Named blocks unique</strong>: Block names must be unique within a schedule</li>
<li><strong>Action exists</strong>: Action references must resolve to defined behaviors</li>
<li><strong>Constraint values</strong>: Temporal constraint values must reference defined enums</li>
<li><strong>Recurrence names unique</strong>: Recurrence names must be unique within a schedule</li>
</ol>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-use-named-blocks-for-overrides"><a class="header" href="#1-use-named-blocks-for-overrides">1. Use Named Blocks for Overrides</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">schedule Extended extends Base {
block { 05:00 - 13:00, action: work } // Appends instead of overriding
}
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">schedule Extended extends Base {
block work { 05:00 - 13:00, action: early_work } // Overrides by name
}
</code></pre>
<h3 id="2-group-related-blocks"><a class="header" href="#2-group-related-blocks">2. Group Related Blocks</a></h3>
<pre><code class="language-storybook">schedule DailyRoutine {
// Sleep blocks
block night_sleep { 22:00 - 05:00, action: sleep }
// Morning blocks
block wake_up { 05:00 - 06:00, action: morning_routine }
block breakfast { 06:00 - 07:00, action: eat_breakfast }
// Work blocks
block commute { 07:00 - 08:00, action: travel_to_work }
block work { 08:00 - 17:00, action: work }
}
</code></pre>
<h3 id="3-use-recurrences-for-special-events"><a class="header" href="#3-use-recurrences-for-special-events">3. Use Recurrences for Special Events</a></h3>
<p><strong>Avoid:</strong> Duplicating blocks manually</p>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">recurs MarketDay on day saturday {
block market { 08:00 - 16:00, action: market_work }
}
</code></pre>
<h3 id="4-extend-for-variations"><a class="header" href="#4-extend-for-variations">4. Extend for Variations</a></h3>
<p><strong>Avoid:</strong> Duplicating entire schedules</p>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">schedule WorkerBase { ... }
schedule EarlyWorker extends WorkerBase { ... }
schedule NightWorker extends WorkerBase { ... }
</code></pre>
<h3 id="5-use-temporal-constraints-for-clarity"><a class="header" href="#5-use-temporal-constraints-for-clarity">5. Use Temporal Constraints for Clarity</a></h3>
<pre><code class="language-storybook">block summer_hours {
06:00 - 20:00
action: long_shift
on season summer // Explicit and readable
}
</code></pre>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./10-characters.html">Characters</a> - Linking schedules to characters</li>
<li><a href="./11-behavior-trees.html">Behavior Trees</a> - Actions reference behavior trees</li>
<li><a href="./18-value-types.html">Value Types</a> - Time and duration literals</li>
<li><a href="../reference/16-other-declarations.html#enums">Enums</a> - Defining seasons, days, months</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Time-based AI</strong>: Schedules drive time-dependent behavior</li>
<li><strong>Template inheritance</strong>: <code>extends</code> enables schedule reuse</li>
<li><strong>Temporal constraints</strong>: Filter blocks by season, day, month</li>
<li><strong>Recurrence patterns</strong>: Define repeating events</li>
<li><strong>Composition</strong>: Build complex schedules from simple parts</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/13-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/15-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="../reference/13-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/15-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,836 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>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="relationships"><a class="header" href="#relationships">Relationships</a></h1>
<p>Relationships define connections between characters, institutions, and other entities. They capture social bonds, power dynamics, emotional ties, and interactive patterns. Relationships can be symmetric (friendship) or asymmetric (parent-child), and support bidirectional perspectives where each participant has different fields.</p>
<h2 id="what-is-a-relationship"><a class="header" href="#what-is-a-relationship">What is a Relationship?</a></h2>
<p>A relationship connects two or more participants and describes:</p>
<ul>
<li><strong>Participants</strong>: The entities involved (characters, institutions)</li>
<li><strong>Roles</strong>: Optional labels (parent, employer, spouse)</li>
<li><strong>Shared fields</strong>: Properties of the relationship itself (bond strength, duration)</li>
<li><strong>Perspective fields</strong>: How each participant views the relationship (<code>self</code>/<code>other</code> blocks)</li>
</ul>
<pre><code>Relationship: ParentChild
├─ Participant: Martha (as parent)
│ ├─ self: { responsibility: 1.0, protective: 0.9 }
│ └─ other: { dependent: 0.8 }
├─ Participant: Tommy (as child)
└─ Shared: { bond: 0.9, years_together: 8 }
</code></pre>
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;relationship-decl&gt; ::= "relationship" &lt;identifier&gt; &lt;body&gt;
&lt;body&gt; ::= "{" &lt;participant&gt;+ &lt;field&gt;* "}"
&lt;participant&gt; ::= &lt;qualified-path&gt; &lt;role-clause&gt;? &lt;perspective-blocks&gt;?
&lt;role-clause&gt; ::= "as" &lt;identifier&gt;
&lt;perspective-blocks&gt; ::= "self" "{" &lt;field&gt;* "}" "other" "{" &lt;field&gt;* "}"
| "self" "{" &lt;field&gt;* "}"
| "other" "{" &lt;field&gt;* "}"
&lt;field&gt; ::= &lt;identifier&gt; ":" &lt;value&gt;
</code></pre>
<h2 id="basic-relationships"><a class="header" href="#basic-relationships">Basic Relationships</a></h2>
<h3 id="simple-two-party-relationship"><a class="header" href="#simple-two-party-relationship">Simple Two-Party Relationship</a></h3>
<pre><code class="language-storybook">relationship Friendship {
Martha
Gregory
bond: 0.8
years_known: 15
}
</code></pre>
<p><strong>Semantics:</strong></p>
<ul>
<li>Two participants: Martha and Gregory</li>
<li>Shared field: <code>bond</code> (strength of friendship)</li>
<li>Shared field: <code>years_known</code> (duration)</li>
</ul>
<h3 id="multi-party-relationship"><a class="header" href="#multi-party-relationship">Multi-Party Relationship</a></h3>
<pre><code class="language-storybook">relationship Family {
Martha
David
Tommy
Elena
household: "Baker Residence"
family_bond: 0.95
}
</code></pre>
<p>Relationships can have any number of participants.</p>
<h2 id="roles"><a class="header" href="#roles">Roles</a></h2>
<p>Roles label a participants function in the relationship.</p>
<h3 id="syntax-1"><a class="header" href="#syntax-1">Syntax</a></h3>
<pre><code class="language-storybook">ParticipantName as role_name
</code></pre>
<h3 id="examples"><a class="header" href="#examples">Examples</a></h3>
<pre><code class="language-storybook">relationship Marriage {
Martha as spouse
David as spouse
bond: 0.9
anniversary: "2015-06-20"
}
</code></pre>
<pre><code class="language-storybook">relationship ParentChild {
Martha as parent
Tommy as child
bond: 0.95
guardianship: true
}
</code></pre>
<pre><code class="language-storybook">relationship EmployerEmployee {
Martha as employer
Elena as employee
workplace: "Martha's Bakery"
salary: 45000
}
</code></pre>
<h3 id="role-semantics"><a class="header" href="#role-semantics">Role Semantics</a></h3>
<ul>
<li><strong>Labels</strong>: Roles are descriptive labels, not types</li>
<li><strong>Multiple roles</strong>: Same person can have different roles in different relationships</li>
<li><strong>Optional</strong>: Roles are optional—participants can be unnamed</li>
<li><strong>Flexibility</strong>: No predefined role vocabulary—use what makes sense</li>
</ul>
<h2 id="perspective-fields-selfother-blocks"><a class="header" href="#perspective-fields-selfother-blocks">Perspective Fields (Self/Other Blocks)</a></h2>
<p>Perspective fields specify how each participant views the relationship. They enable asymmetric, bidirectional relationships.</p>
<h3 id="syntax-2"><a class="header" href="#syntax-2">Syntax</a></h3>
<pre><code class="language-storybook">ParticipantName as role self {
// Fields describing this participant's perspective
} other {
// Fields describing how this participant views others
}
</code></pre>
<h3 id="self-block"><a class="header" href="#self-block">Self Block</a></h3>
<p>The <code>self</code> block contains fields about how the participant experiences the relationship:</p>
<pre><code class="language-storybook">relationship ParentChild {
Martha as parent self {
responsibility: 1.0
protective: 0.9
anxiety_level: 0.6
}
Tommy as child
}
</code></pre>
<p><strong>Marthas perspective:</strong></p>
<ul>
<li><code>responsibility</code>: She feels 100% responsible</li>
<li><code>protective</code>: Shes highly protective (90%)</li>
<li><code>anxiety_level</code>: Moderate anxiety about parenting</li>
</ul>
<h3 id="other-block"><a class="header" href="#other-block">Other Block</a></h3>
<p>The <code>other</code> block contains fields about how the participant views the other participants:</p>
<pre><code class="language-storybook">relationship ParentChild {
Martha as parent self {
responsibility: 1.0
} other {
dependent: 0.8 // She sees Tommy as 80% dependent
mature_for_age: 0.7 // She thinks he's fairly mature
}
Tommy as child
}
</code></pre>
<h3 id="both-blocks"><a class="header" href="#both-blocks">Both Blocks</a></h3>
<pre><code class="language-storybook">relationship EmployerEmployee {
Martha as employer self {
authority: 0.9
stress: 0.6
} other {
respect: 0.8
trust: 0.85
}
Elena as employee
}
</code></pre>
<p><strong>Marthas perspective:</strong></p>
<ul>
<li><strong>Self</strong>: She has high authority (90%), moderate stress (60%)</li>
<li><strong>Other</strong>: She respects Elena (80%), trusts her (85%)</li>
</ul>
<h3 id="asymmetric-relationships"><a class="header" href="#asymmetric-relationships">Asymmetric Relationships</a></h3>
<p>Different participants can have different perspective fields:</p>
<pre><code class="language-storybook">relationship TeacherStudent {
Gandalf as teacher self {
patience: 0.8
wisdom_to_impart: 1.0
} other {
potential: 0.9
ready_to_learn: 0.6
}
Frodo as student self {
eager_to_learn: 0.7
overwhelmed: 0.5
} other {
admiration: 0.95
intimidated: 0.4
}
bond: 0.85
}
</code></pre>
<p><strong>Gandalfs view:</strong></p>
<ul>
<li>Hes patient (80%), has much wisdom to share</li>
<li>Sees Frodo as having high potential but only moderately ready</li>
</ul>
<p><strong>Frodos view:</strong></p>
<ul>
<li>Hes eager but overwhelmed</li>
<li>Deeply admires Gandalf, slightly intimidated</li>
</ul>
<h2 id="shared-fields"><a class="header" href="#shared-fields">Shared Fields</a></h2>
<p>Fields declared at the relationship level (not in <code>self</code>/<code>other</code> blocks) are <strong>shared</strong> among all participants.</p>
<pre><code class="language-storybook">relationship Friendship {
Martha
Gregory
bond: 0.8 // Shared: mutual bond strength
years_known: 15 // Shared: how long they've known each other
shared_routines: 3 // Shared: number of daily routines
}
</code></pre>
<p><strong>Common shared fields:</strong></p>
<ul>
<li><code>bond</code>: Relationship strength (0.0 to 1.0)</li>
<li><code>years_known</code>: Duration of relationship</li>
<li><code>trust</code>: Mutual trust level</li>
<li><code>commitment</code>: Commitment to the relationship</li>
<li><code>compatibility</code>: How well they get along</li>
</ul>
<h2 id="prose-blocks"><a class="header" href="#prose-blocks">Prose Blocks</a></h2>
<p>Relationships can include prose blocks for narrative context.</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.
---
}
}
</code></pre>
<p><strong>Common prose tags:</strong></p>
<ul>
<li><code>---perspective</code>: How a participant views the relationship</li>
<li><code>---history</code>: Background of the relationship</li>
<li><code>---dynamics</code>: How the relationship functions</li>
<li><code>---notes</code>: Design or development notes</li>
</ul>
<p>Note: In this syntax, perspective fields are inside participant blocks (not separate <code>self</code>/<code>other</code> blocks).</p>
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
<h3 id="simple-friendship"><a class="header" href="#simple-friendship">Simple Friendship</a></h3>
<pre><code class="language-storybook">relationship Friendship {
Martha
NeighborBaker
bond: 0.6
years_known: 10
}
</code></pre>
<h3 id="marriage-with-roles"><a class="header" href="#marriage-with-roles">Marriage with Roles</a></h3>
<pre><code class="language-storybook">relationship Marriage {
Martha as spouse
David as spouse
bond: 0.9
anniversary: "2015-06-20"
children: 2
}
</code></pre>
<h3 id="parent-child-with-perspectives"><a class="header" href="#parent-child-with-perspectives">Parent-Child with Perspectives</a></h3>
<pre><code class="language-storybook">relationship ParentChild {
Martha as parent self {
responsibility: 1.0
protective: 0.9
anxiety_level: 0.6
} other {
dependent: 0.8
mature_for_age: 0.7
}
Tommy as child self {
seeks_independence: 0.7
appreciates_parent: 0.9
} other {
feels_understood: 0.6
wants_more_freedom: 0.8
}
bond: 0.95
years_together: 8
}
</code></pre>
<h3 id="employer-employee"><a class="header" href="#employer-employee">Employer-Employee</a></h3>
<pre><code class="language-storybook">relationship EmployerEmployee {
Martha as employer self {
authority: 0.9
satisfaction_with_employee: 0.85
} other {
respect: 0.8
trust: 0.85
}
Elena as employee self {
job_satisfaction: 0.8
career_growth: 0.7
} other {
respects_boss: 0.9
appreciates_flexibility: 0.95
}
workplace: "Martha's Bakery"
salary: 45000
employment_start: "2023-01-15"
}
</code></pre>
<h3 id="complex-multi-perspective"><a class="header" href="#complex-multi-perspective">Complex Multi-Perspective</a></h3>
<pre><code class="language-storybook">relationship MentorApprentice {
Martha {
role: mentor
teaching_style: "patient"
investment: 0.9
---perspective
Martha sees Elena as the daughter she might have had in
the trade. She recognizes the same passion she felt at
that age and pushes Elena harder because she knows the
talent is there. Every correction comes from love.
---
}
Elena {
role: apprentice
dedication: 0.9
anxiety: 0.4
---perspective
Elena idolizes Martha's skill but fears disappointing
her. Every morning she arrives thirty minutes early to
practice techniques before Martha gets in. She keeps a
notebook of every correction, reviewing them each night.
"I want to be half as good as her someday" -- this quiet
ambition drives everything Elena does.
---
}
bond: 0.85
}
</code></pre>
<h3 id="business-rivalry"><a class="header" href="#business-rivalry">Business Rivalry</a></h3>
<pre><code class="language-storybook">relationship BakeryRivalry {
Martha {
role: established_baker
aware_of_competition: true
respects_rival: 0.6
---perspective
Martha views the new bakery across town as healthy
competition. She respects their pastry work but knows
her sourdough is unmatched. The rivalry pushes her to
keep innovating.
---
}
RivalBaker {
role: newcomer
wants_to_surpass: true
studies_martha: 0.8
---perspective
The rival baker moved to town specifically because of
Martha's reputation. They study her techniques, buy her
bread to analyze it, and dream of the day a customer
chooses their loaf over Martha's sourdough.
---
}
bond: 0.3
}
</code></pre>
<h3 id="multi-party-family"><a class="header" href="#multi-party-family">Multi-Party Family</a></h3>
<pre><code class="language-storybook">relationship BakerFamily {
Martha as parent
David as parent
Tommy as child
Elena as child
household: "Baker Residence"
family_bond: 0.95
dinner_time: 18:00
---dynamics
A loving queer family running a bakery together. Martha and
David met at culinary school, married, and adopted Tommy and
Elena. The whole family works at the bakery on weekends.
---
}
</code></pre>
<h3 id="co-owners-partnership"><a class="header" href="#co-owners-partnership">Co-Owners Partnership</a></h3>
<pre><code class="language-storybook">relationship BakeryPartnership {
Martha {
role: co_owner
specialty: "bread"
handles_finances: true
---perspective
Martha and Jane complement each other perfectly. Martha
handles the bread and business side while Jane creates
the pastries that draw customers in. Together they have
built something neither could alone.
---
}
Jane {
role: co_owner
specialty: "pastries"
handles_creativity: true
---perspective
Jane considers Martha the steady foundation of their
partnership. While Jane experiments and creates, Martha
ensures the bakery runs like clockwork. Their different
strengths make the bakery stronger.
---
}
bond: 0.9
}
</code></pre>
<h2 id="participant-types"><a class="header" href="#participant-types">Participant Types</a></h2>
<p>Participants can be:</p>
<ol>
<li><strong>Characters</strong>: Most common</li>
<li><strong>Institutions</strong>: Organizations in relationships</li>
<li><strong>Locations</strong>: Less common, but valid (e.g., “GuardianOfPlace”)</li>
</ol>
<pre><code class="language-storybook">relationship GuildMembership {
Elena as member
BakersGuild as organization
membership_since: "2023-01-01"
standing: "good"
dues_paid: true
}
</code></pre>
<h2 id="field-resolution"><a class="header" href="#field-resolution">Field Resolution</a></h2>
<p>When a relationship is resolved, fields are merged:</p>
<ol>
<li><strong>Relationship shared fields</strong> apply to all participants</li>
<li><strong>Participant <code>self</code> blocks</strong> apply to that participant</li>
<li><strong>Participant <code>other</code> blocks</strong> describe how that participant views others</li>
</ol>
<p><strong>Example:</strong></p>
<pre><code class="language-storybook">relationship Example {
Martha as friend self {
loyalty: 0.9
} other {
trust: 0.8
}
Gregory as friend
bond: 0.85
}
</code></pre>
<p><strong>Resolution:</strong></p>
<ul>
<li>Martha gets: <code>loyalty: 0.9</code> (from self), <code>trust: 0.8</code> (towards Gregory, from other), <code>bond: 0.85</code> (shared)</li>
<li>Gregory gets: <code>bond: 0.85</code> (shared)</li>
<li>Relationship gets: <code>bond: 0.85</code></li>
</ul>
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>At least two participants</strong>: Relationships require ≥2 participants</li>
<li><strong>Participants exist</strong>: All participant names must reference defined entities</li>
<li><strong>Unique participant names</strong>: Each participant appears at most once</li>
<li><strong>Field type consistency</strong>: Fields must have valid value types</li>
<li><strong>No circular relationships</strong>: Avoid infinite relationship chains (warning)</li>
<li><strong>Self/other completeness</strong>: If using <code>self</code>/<code>other</code>, both blocks should be present (best practice)</li>
</ol>
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
<h3 id="social-networks"><a class="header" href="#social-networks">Social Networks</a></h3>
<pre><code class="language-storybook">relationship BakerFriendship {
Martha
Gregory
bond: 0.8
}
relationship SupplierPartnership {
Martha
Farmer_Jenkins
bond: 0.7
}
relationship BakeryRivalry {
Martha
RivalBaker
bond: 0.2
competitive: true
}
</code></pre>
<h3 id="family-structures"><a class="header" href="#family-structures">Family Structures</a></h3>
<pre><code class="language-storybook">relationship ParentChild {
Martha as parent
Tommy as child
bond: 0.95
}
relationship Siblings {
Tommy as older_sibling
Elena as younger_sibling
bond: 0.85
rivalry: 0.3
}
</code></pre>
<h3 id="power-dynamics"><a class="header" href="#power-dynamics">Power Dynamics</a></h3>
<pre><code class="language-storybook">relationship Vassalage {
King as lord self {
grants: "protection"
expects: "loyalty"
} other {
trusts: 0.6
}
Knight as vassal self {
swears: "fealty"
expects: "land"
} other {
respects: 0.9
}
oath_date: "1205-03-15"
}
</code></pre>
<h3 id="asymmetric-awareness"><a class="header" href="#asymmetric-awareness">Asymmetric Awareness</a></h3>
<pre><code class="language-storybook">relationship StalkingVictim {
Stalker as pursuer self {
obsession: 0.95
distance_maintained: 50 // meters
} other {
believes_unnoticed: true
}
Victim as unaware_target self {
awareness: 0.0
}
danger_level: 0.8
}
</code></pre>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-use-descriptive-relationship-names"><a class="header" href="#1-use-descriptive-relationship-names">1. Use Descriptive Relationship Names</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">relationship R1 { ... }
relationship MarthaGregory { ... }
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">relationship Friendship { ... }
relationship ParentChild { ... }
relationship MentorApprentice { ... }
</code></pre>
<h3 id="2-use-roles-for-clarity"><a class="header" href="#2-use-roles-for-clarity">2. Use Roles for Clarity</a></h3>
<pre><code class="language-storybook">relationship Marriage {
Martha as spouse
David as spouse
}
</code></pre>
<p>Better than:</p>
<pre><code class="language-storybook">relationship Marriage {
Martha
David
}
</code></pre>
<h3 id="3-shared-fields-for-mutual-properties"><a class="header" href="#3-shared-fields-for-mutual-properties">3. Shared Fields for Mutual Properties</a></h3>
<pre><code class="language-storybook">relationship Partnership {
Martha
Jane
bond: 0.9 // Mutual bond
years_together: 5 // Shared history
}
</code></pre>
<h3 id="4-selfother-for-perspectives"><a class="header" href="#4-selfother-for-perspectives">4. Self/Other for Perspectives</a></h3>
<pre><code class="language-storybook">relationship TeacherStudent {
Teacher as mentor self {
patience: 0.8
} other {
potential: 0.9
}
Student as learner self {
motivation: 0.7
} other {
admiration: 0.95
}
}
</code></pre>
<h3 id="5-prose-blocks-for-rich-context"><a class="header" href="#5-prose-blocks-for-rich-context">5. Prose Blocks for Rich Context</a></h3>
<pre><code class="language-storybook">relationship ComplexDynamic {
Martha { ... }
Jane { ... }
---dynamics
Their relationship is characterized by mutual respect but
divergent goals. Martha focuses on traditional bread while Jane
pushes for experimental pastries, creating creative tension.
---
}
</code></pre>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./10-characters.html">Characters</a> - Participants are typically characters</li>
<li><a href="./16-other-declarations.html#institutions">Institutions</a> - Institutions can participate</li>
<li><a href="./18-value-types.html">Value Types</a> - Field value types</li>
<li><a href="./17-expressions.html">Expression Language</a> - Querying relationships</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Bidirectional modeling</strong>: <code>self</code>/<code>other</code> blocks enable asymmetric perspectives</li>
<li><strong>Social simulation</strong>: Relationships drive character interactions</li>
<li><strong>Narrative depth</strong>: Prose blocks embed storytelling in relationship data</li>
<li><strong>Power dynamics</strong>: Roles and perspective fields model hierarchies</li>
<li><strong>Emotional bonds</strong>: Bond strength and trust metrics quantify connections</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/14-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="../reference/16a-locations.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../reference/14-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="../reference/16a-locations.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>

File diff suppressed because it is too large Load Diff

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>Locations - 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"><a class="header" href="#locations">Locations</a></h1>
<p>Locations define places in your world rooms, buildings, cities, landscapes, or abstract spaces. They provide spatial context for characters, events, and narratives.</p>
<hr />
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;location-decl&gt; ::= "location" &lt;identifier&gt; "{" &lt;field&gt;* &lt;prose-block&gt;* "}"
</code></pre>
<p>A location declaration consists of:</p>
<ul>
<li>The <code>location</code> keyword</li>
<li>A unique name (identifier)</li>
<li>A body block containing fields and optional prose blocks</li>
</ul>
<p>Locations are one of the simpler declaration types they hold fields and prose blocks but do not support resource linking (<code>uses behaviors</code> / <code>uses schedule</code>) like characters or institutions.</p>
<hr />
<h2 id="basic-location"><a class="header" href="#basic-location">Basic Location</a></h2>
<p>The simplest location has a name and descriptive fields:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
capacity: 30
address: "14 Main Street"
open_hours: "06:00-18:00"
}
</code></pre>
<p>Fields can use any <a href="./18-value-types.html">value type</a>: integers, floats, strings, booleans, enums, lists, ranges, and durations.</p>
<hr />
<h2 id="fields"><a class="header" href="#fields">Fields</a></h2>
<p>Location fields describe properties of the place:</p>
<pre><code class="language-storybook">location BakerHome {
type: residence
bedrooms: 3
has_garden: true
garden_size_sqft: 450.0
residents: ["Martha", "David", "Jane"]
comfort_level: 0.85
}
</code></pre>
<h3 id="common-field-patterns"><a class="header" href="#common-field-patterns">Common Field Patterns</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody>
<tr><td><code>type</code></td><td>enum/identifier</td><td>Category of the place</td></tr>
<tr><td><code>capacity</code></td><td>integer</td><td>How many can be in this place</td></tr>
<tr><td><code>size</code></td><td>enum/float</td><td>Physical size</td></tr>
<tr><td><code>coordinates</code></td><td>integer/float</td><td>Position in a world map</td></tr>
<tr><td><code>accessible</code></td><td>boolean</td><td>Whether characters can enter</td></tr>
</tbody></table>
</div>
<p>These are conventions, not enforced schema. You define whatever fields are meaningful for your world.</p>
<hr />
<h2 id="prose-blocks"><a class="header" href="#prose-blocks">Prose Blocks</a></h2>
<p>Locations support prose blocks for rich narrative content. Prose blocks are delimited by <code>---tag</code> markers:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
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.
---
---atmosphere
Flour dust catches the light from tall windows. A display case
holds rows of golden pastries. Behind the counter, the kitchen
hums with activity from 4 AM onward.
---
}
</code></pre>
<p><strong>Prose block rules:</strong></p>
<ul>
<li>Start with <code>---tag_name</code> on its own line</li>
<li>Content is free-form text (Markdown supported)</li>
<li>End with <code>---</code> on its own line</li>
<li>The tag name becomes the key for retrieval</li>
<li>Multiple prose blocks per location are allowed</li>
<li>Each tag must be unique within the location</li>
</ul>
<hr />
<h2 id="ranges"><a class="header" href="#ranges">Ranges</a></h2>
<p>Locations can use range values for procedural variation:</p>
<pre><code class="language-storybook">location MarketSquare {
type: outdoor_market
stalls: 10..25
daily_visitors: 50..200
noise_level: 0.4..0.9
}
</code></pre>
<p>When instantiated, values are selected from within the specified range. This is useful for locations that should vary across simulation runs.</p>
<hr />
<h2 id="lists"><a class="header" href="#lists">Lists</a></h2>
<p>Locations can hold list values:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
products: ["sourdough", "croissants", "rye bread", "cinnamon rolls"]
equipment: ["stone oven", "mixing station", "proofing cabinet"]
}
</code></pre>
<hr />
<h2 id="referencing-other-entities"><a class="header" href="#referencing-other-entities">Referencing Other Entities</a></h2>
<p>Location fields can reference other declarations by name:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
owner: Martha
neighborhood: MainStreet
part_of: TownCenter
}
</code></pre>
<p>These are identifier references they are not validated as cross-references at parse time, but the resolver checks that referenced names exist in the name table.</p>
<hr />
<h2 id="nested-structure-with-fields"><a class="header" href="#nested-structure-with-fields">Nested Structure with Fields</a></h2>
<p>You can model spatial hierarchy using fields:</p>
<pre><code class="language-storybook">location BakersBakery {
type: bakery
parent: MainStreet
// Zones within the bakery
has_kitchen: true
has_storefront: true
has_storage_room: true
// Dimensions
total_sqft: 1200
kitchen_sqft: 600
storefront_sqft: 400
storage_sqft: 200
}
location MainStreet {
type: street
parent: TownCenter
length_meters: 500
shops: 12
}
location TownCenter {
type: district
population: 2000
}
</code></pre>
<p>There is no built-in parent-child relationship for locations you model hierarchy through conventional field names like <code>part_of</code>, <code>parent</code>, or <code>contains</code>.</p>
<hr />
<h2 id="location-with-enum-fields"><a class="header" href="#location-with-enum-fields">Location with Enum Fields</a></h2>
<p>Use <a href="./16-other-declarations.html#enums">enums</a> for type-safe categorization:</p>
<pre><code class="language-storybook">enum PlaceType {
residence, shop, workshop, office,
park, street, square, church
}
enum Accessibility {
public, private, restricted, members_only
}
location BakersBakery {
type: shop
accessibility: public
capacity: 30
}
location BakerHome {
type: residence
accessibility: private
capacity: 8
}
</code></pre>
<hr />
<h2 id="complete-example-baker-family-locations"><a class="header" href="#complete-example-baker-family-locations">Complete Example: Baker Family Locations</a></h2>
<pre><code class="language-storybook">use schema::enums::{PlaceType, Accessibility};
location BakersBakery {
type: shop
accessibility: public
owner: Martha
address: "14 Main Street"
capacity: 30
employees: 4
established: "2011"
specialty: "artisan sourdough"
daily_output_loaves: 80..120
---description
Martha Baker's artisan bakery, known throughout town for its
sourdough and pastries. The shop opens at 6 AM sharp, and by
mid-morning there's usually a line out the door.
---
---history
Originally a hardware store, Martha converted the space after
winning a local baking competition. The stone oven was imported
from France and is the heart of the operation.
---
}
location BakerHome {
type: residence
accessibility: private
address: "22 Elm Lane"
bedrooms: 4
has_garden: true
garden_size_sqft: 600
residents: ["Martha", "David", "Jane", "Tom"]
comfort_level: 0.9
---description
A comfortable family home on a quiet street. The kitchen is
oversized (Martha insisted) and there's always something
baking, even at home.
---
}
location BakersGuildHall {
type: office
accessibility: members_only
address: "7 Guild Row"
capacity: 100
meeting_room_capacity: 40
established: "1892"
---description
The historic headquarters of the Bakers Guild, where trade
matters are discussed and apprenticeships are arranged.
---
}
</code></pre>
<hr />
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>Unique names</strong>: Location names must be unique within their scope</li>
<li><strong>Valid field values</strong>: All fields must have values that conform to <a href="./18-value-types.html">value types</a></li>
<li><strong>Unique field names</strong>: No duplicate field names within a location</li>
<li><strong>Unique prose tags</strong>: No duplicate prose block tags within a location</li>
<li><strong>Valid identifiers</strong>: Location names must follow identifier rules (<code>[a-zA-Z_][a-zA-Z0-9_]*</code>)</li>
</ol>
<hr />
<h2 id="locations-vs-other-declarations"><a class="header" href="#locations-vs-other-declarations">Locations vs. Other Declarations</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Aspect</th><th>Locations</th><th>Institutions</th><th>Characters</th></tr></thead><tbody>
<tr><td>Purpose</td><td>Physical/abstract places</td><td>Organizations/groups</td><td>Individuals</td></tr>
<tr><td>Resource linking</td><td>No</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Prose blocks</td><td>Yes</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Species</td><td>No</td><td>No</td><td>Yes</td></tr>
<tr><td>Templates</td><td>No</td><td>No</td><td>Yes</td></tr>
</tbody></table>
</div>
<p>Locations are intentionally simple. They define <em>where</em> things happen. For <em>who</em> does things, use <a href="./10-characters.html">characters</a>. For <em>organizational structures</em>, use <a href="./16b-institutions.html">institutions</a>.</p>
<hr />
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./10-characters.html">Characters</a> Characters can reference locations via fields</li>
<li><a href="./16b-institutions.html">Institutions</a> Institutions can be associated with locations</li>
<li><a href="./14-schedules.html">Schedules</a> Schedule activities can reference locations</li>
<li><a href="./18-value-types.html">Value Types</a> All field value types</li>
<li><a href="./16-other-declarations.html">Other Declarations</a> Overview of all utility declarations</li>
<li><a href="./19-validation.html">Validation Rules</a> Complete validation reference</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/15-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="../reference/16b-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="../reference/15-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="../reference/16b-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,569 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>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="institutions"><a class="header" href="#institutions">Institutions</a></h1>
<p>Institutions define organizations, groups, and systems entities that function like characters but represent collectives rather than individuals. Unlike locations (which model <em>where</em>), institutions model <em>what structures and organizations</em> operate in your world.</p>
<hr />
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;institution-decl&gt; ::= "institution" &lt;identifier&gt; "{" &lt;institution-body&gt; "}"
&lt;institution-body&gt; ::= (&lt;field&gt; | &lt;uses-behaviors&gt; | &lt;uses-schedule&gt; | &lt;prose-block&gt;)*
&lt;uses-behaviors&gt; ::= "uses" "behaviors" ":" "[" &lt;behavior-link&gt; ("," &lt;behavior-link&gt;)* "]"
&lt;behavior-link&gt; ::= "{" "tree" ":" &lt;path&gt; ","?
("when" ":" &lt;expression&gt; ","?)?
("priority" ":" &lt;priority-level&gt; ","?)? "}"
&lt;priority-level&gt; ::= "low" | "normal" | "high" | "critical"
&lt;uses-schedule&gt; ::= "uses" "schedule" ":" &lt;identifier&gt;
| "uses" "schedules" ":" "[" &lt;identifier&gt; ("," &lt;identifier&gt;)* "]"
</code></pre>
<p>An institution declaration consists of:</p>
<ul>
<li>The <code>institution</code> keyword</li>
<li>A unique name (identifier)</li>
<li>A body block containing fields, resource links, and optional prose blocks</li>
</ul>
<hr />
<h2 id="basic-institution"><a class="header" href="#basic-institution">Basic Institution</a></h2>
<p>The simplest institution has a name and descriptive fields:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
members: 50
founded: "1892"
reputation: 0.85
}
</code></pre>
<hr />
<h2 id="fields"><a class="header" href="#fields">Fields</a></h2>
<p>Institution fields describe properties of the organization:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
members: 50
founded: "1892"
reputation: 0.85
dues_annual: 120
meeting_frequency: "monthly"
accepts_apprentices: true
max_apprentices: 10
location: BakersGuildHall
}
</code></pre>
<h3 id="common-field-patterns"><a class="header" href="#common-field-patterns">Common Field Patterns</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody>
<tr><td><code>type</code></td><td>enum/identifier</td><td>Category of organization</td></tr>
<tr><td><code>members</code></td><td>integer</td><td>Membership count</td></tr>
<tr><td><code>founded</code></td><td>string</td><td>Establishment date</td></tr>
<tr><td><code>reputation</code></td><td>float</td><td>Public standing (0.0-1.0)</td></tr>
<tr><td><code>location</code></td><td>identifier</td><td>Where the institution operates</td></tr>
<tr><td><code>leader</code></td><td>identifier</td><td>Who runs the institution</td></tr>
</tbody></table>
</div>
<p>These are conventions you define whatever fields make sense for your world.</p>
<hr />
<h2 id="prose-blocks"><a class="header" href="#prose-blocks">Prose Blocks</a></h2>
<p>Institutions support prose blocks for rich narrative documentation:</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 with suppliers.
---
---charter
Article I: All members shall maintain the highest standards of
baking quality. Article II: Apprentices must complete a three-year
training program. Article III: Monthly meetings are mandatory.
---
---traditions
Every autumn, the Guild hosts the Great Bake-Off, a competition
open to all members. The winner earns the title of Master Baker
for the following year.
---
}
</code></pre>
<p><strong>Prose block rules:</strong></p>
<ul>
<li>Start with <code>---tag_name</code> on its own line</li>
<li>Content is free-form text (Markdown supported)</li>
<li>End with <code>---</code> on its own line</li>
<li>Multiple prose blocks per institution are allowed</li>
<li>Each tag must be unique within the institution</li>
</ul>
<hr />
<h2 id="resource-linking-behaviors"><a class="header" href="#resource-linking-behaviors">Resource Linking: Behaviors</a></h2>
<p>Institutions can link to <a href="./11-behavior-trees.html">behavior trees</a> using the <code>uses behaviors</code> clause. This defines what actions the institution takes as a collective entity.</p>
<h3 id="simple-behavior-linking"><a class="header" href="#simple-behavior-linking">Simple Behavior Linking</a></h3>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
members: 50
uses behaviors: [
{ tree: ManageApprentices },
{ tree: NegotiateSuppliers },
{ tree: HostEvents }
]
}
</code></pre>
<p>Each behavior link is an object with a <code>tree</code> field referencing a behavior tree by name or path.</p>
<h3 id="behavior-links-with-priority"><a class="header" href="#behavior-links-with-priority">Behavior Links with Priority</a></h3>
<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: <code>low</code>, <code>normal</code> (default), <code>high</code>, <code>critical</code>.</p>
<h3 id="conditional-behavior-links"><a class="header" href="#conditional-behavior-links">Conditional Behavior Links</a></h3>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
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 takes an <a href="./17-expressions.html">expression</a> that determines when the behavior activates.</p>
<h3 id="behavior-link-fields"><a class="header" href="#behavior-link-fields">Behavior Link Fields</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Field</th><th>Required</th><th>Type</th><th>Description</th></tr></thead><tbody>
<tr><td><code>tree</code></td><td>Yes</td><td>path</td><td>Reference to a behavior tree</td></tr>
<tr><td><code>priority</code></td><td>No</td><td>priority level</td><td>Execution priority (default: <code>normal</code>)</td></tr>
<tr><td><code>when</code></td><td>No</td><td>expression</td><td>Activation condition</td></tr>
</tbody></table>
</div>
<hr />
<h2 id="resource-linking-schedules"><a class="header" href="#resource-linking-schedules">Resource Linking: Schedules</a></h2>
<p>Institutions can link to <a href="./14-schedules.html">schedules</a> using the <code>uses schedule</code> or <code>uses schedules</code> clause.</p>
<h3 id="single-schedule"><a class="header" href="#single-schedule">Single Schedule</a></h3>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
uses schedule: GuildOperatingHours
}
</code></pre>
<h3 id="multiple-schedules"><a class="header" href="#multiple-schedules">Multiple Schedules</a></h3>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
uses schedules: [WeekdaySchedule, WeekendSchedule, HolidaySchedule]
}
</code></pre>
<hr />
<h2 id="complete-syntax-example"><a class="header" href="#complete-syntax-example">Complete Syntax Example</a></h2>
<p>An institution using all available features:</p>
<pre><code class="language-storybook">institution BakersGuild {
type: trade_guild
members: 50
founded: "1892"
reputation: 0.85
dues_annual: 120
location: BakersGuildHall
leader: Martha
accepts_apprentices: true
uses behaviors: [
{ tree: ManageApprentices, priority: normal },
{ tree: NegotiateSuppliers, priority: high },
{ tree: HostAnnualBakeOff, when: month is october, priority: high },
{ tree: EmergencyMeeting, when: reputation &lt; 0.3, priority: critical }
]
uses schedule: GuildOperatingHours
---description
The Bakers Guild has been the backbone of the town's bread trade
since 1892. Martha Baker currently serves as Guild Master.
---
---membership_rules
New members must be nominated by an existing member and pass
a baking trial. Apprentices are accepted from age 16.
---
}
</code></pre>
<hr />
<h2 id="membership-modeling"><a class="header" href="#membership-modeling">Membership Modeling</a></h2>
<p>Institutions themselves dont have a built-in membership list. Instead, model membership through character fields or relationships:</p>
<h3 id="via-character-fields"><a class="header" href="#via-character-fields">Via Character Fields</a></h3>
<pre><code class="language-storybook">character Martha {
age: 45
guild_member: true
guild_role: guild_master
guild: BakersGuild
}
character Jane {
age: 19
guild_member: true
guild_role: apprentice
guild: BakersGuild
}
</code></pre>
<h3 id="via-relationships"><a class="header" href="#via-relationships">Via Relationships</a></h3>
<pre><code class="language-storybook">relationship GuildMembership {
Martha as guild_master { }
BakersGuild as organization { }
bond: 0.95
years_active: 20
}
relationship Apprenticeship {
Jane as apprentice { }
BakersGuild as guild { }
Martha as mentor { }
years_completed: 1
years_remaining: 2
}
</code></pre>
<hr />
<h2 id="institutions-vs-characters"><a class="header" href="#institutions-vs-characters">Institutions vs. Characters</a></h2>
<p>Both institutions and characters can use behaviors and schedules, but they serve different purposes:</p>
<div class="table-wrapper"><table><thead><tr><th>Aspect</th><th>Institutions</th><th>Characters</th></tr></thead><tbody>
<tr><td>Represents</td><td>Organizations, collectives</td><td>Individuals</td></tr>
<tr><td>Species</td><td>No</td><td>Yes (optional)</td></tr>
<tr><td>Templates</td><td>No</td><td>Yes (optional)</td></tr>
<tr><td><code>uses behaviors</code></td><td>Yes</td><td>Yes</td></tr>
<tr><td><code>uses schedule</code></td><td>Yes</td><td>Yes</td></tr>
<tr><td>Prose blocks</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Participation in relationships</td><td>Yes (via name)</td><td>Yes (via name)</td></tr>
</tbody></table>
</div>
<p>Use institutions when you need an entity that acts collectively: a guild votes, a government issues decrees, a school enrolls students.</p>
<hr />
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
<ul>
<li><strong>Organizations</strong>: Guilds, companies, governments, religions</li>
<li><strong>Social structures</strong>: Families, tribes, castes, classes</li>
<li><strong>Systems</strong>: Economic systems, magical systems, legal frameworks</li>
<li><strong>Abstract entities</strong>: Dream logic, fate, cultural norms</li>
<li><strong>Collectives</strong>: Military units, sports teams, musical ensembles</li>
</ul>
<hr />
<h2 id="complete-example-baker-family-world"><a class="header" href="#complete-example-baker-family-world">Complete Example: Baker Family World</a></h2>
<pre><code class="language-storybook">use schema::enums::{GuildRole, InstitutionType};
institution BakersGuild {
type: trade_guild
members: 50
founded: "1892"
reputation: 0.85
location: BakersGuildHall
leader: Martha
annual_dues: 120
accepts_apprentices: true
max_apprentices_per_mentor: 2
uses behaviors: [
{ tree: ManageApprentices, priority: normal },
{ tree: NegotiateSuppliers, priority: high },
{ tree: HostAnnualBakeOff, when: month is october, priority: high }
]
uses schedule: GuildOperatingHours
---description
The Bakers Guild has served the town since 1892, ensuring quality
standards and fair pricing across all bakeries. Monthly meetings
are held at the Guild Hall, and the annual Bake-Off is the
highlight of the autumn calendar.
---
}
institution TownCouncil {
type: government
members: 12
meets: "first Monday of each month"
location: TownHall
jurisdiction: "town limits"
uses behaviors: [
{ tree: ReviewPetitions },
{ tree: AllocateBudget, priority: high }
]
---description
The elected body governing the town. Martha Baker has been
lobbying them for years about flour import tariffs.
---
}
institution BakersBakeryBusiness {
type: business
owner: Martha
employees: 4
location: BakersBakery
annual_revenue: 180000
established: "2011"
uses behaviors: [
{ tree: DailyBakingOps, priority: high },
{ tree: InventoryManagement },
{ tree: CustomerService }
]
uses schedule: BakeryOperatingHours
---description
The business entity behind Baker's Bakery. Martha runs the
operation with help from her daughter Jane (apprentice baker),
two full-time bakers, and a part-time cashier.
---
}
</code></pre>
<hr />
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>Unique names</strong>: Institution names must be unique within their scope</li>
<li><strong>Valid field values</strong>: All fields must have values conforming to <a href="./18-value-types.html">value types</a></li>
<li><strong>Unique field names</strong>: No duplicate field names within an institution</li>
<li><strong>Unique prose tags</strong>: No duplicate prose block tags within an institution</li>
<li><strong>Valid behavior links</strong>: Each behavior link must have a <code>tree</code> field</li>
<li><strong>Valid priority levels</strong>: Priority must be <code>low</code>, <code>normal</code>, <code>high</code>, or <code>critical</code></li>
<li><strong>Valid schedule references</strong>: Schedule names in <code>uses schedule</code>/<code>uses schedules</code> must be valid identifiers</li>
<li><strong>Valid identifiers</strong>: Institution names must follow identifier rules (<code>[a-zA-Z_][a-zA-Z0-9_]*</code>)</li>
</ol>
<hr />
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./10-characters.html">Characters</a> Characters can be members of institutions</li>
<li><a href="./16a-locations.html">Locations</a> Institutions can be associated with locations</li>
<li><a href="./11-behavior-trees.html">Behavior Trees</a> Institutions can use behaviors</li>
<li><a href="./14-schedules.html">Schedules</a> Institutions can use schedules</li>
<li><a href="./15-relationships.html">Relationships</a> Model membership via relationships</li>
<li><a href="./17-expressions.html">Expressions</a> Conditional behavior activation</li>
<li><a href="./18-value-types.html">Value Types</a> All field value types</li>
<li><a href="./16-other-declarations.html">Other Declarations</a> Overview of all utility declarations</li>
<li><a href="./19-validation.html">Validation Rules</a> Complete validation reference</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/16a-locations.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/16-other-declarations.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../reference/16a-locations.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/16-other-declarations.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,745 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Expression Language - 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="expression-language"><a class="header" href="#expression-language">Expression Language</a></h1>
<p>The Storybook expression language enables conditions, queries, and logic throughout the system. Expressions appear in life arc transitions, behavior tree guards, decorator conditions, and relationship queries. This chapter provides a complete reference for expression syntax and semantics.</p>
<h2 id="what-are-expressions"><a class="header" href="#what-are-expressions">What are Expressions?</a></h2>
<p>Expressions are logical statements that evaluate to <code>true</code> or <code>false</code>. They combine:</p>
<ul>
<li><strong>Literals</strong>: Numbers, strings, booleans</li>
<li><strong>Identifiers</strong>: References to fields and entities</li>
<li><strong>Comparisons</strong>: <code>==</code>, <code>!=</code>, <code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code></li>
<li><strong>Logical operators</strong>: <code>and</code>, <code>or</code>, <code>not</code></li>
<li><strong>Field access</strong>: <code>self.field</code>, <code>other.field</code></li>
<li><strong>Quantifiers</strong>: <code>forall</code>, <code>exists</code> (for collections)</li>
</ul>
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
<pre><code class="language-bnf">&lt;expression&gt; ::= &lt;literal&gt;
| &lt;identifier&gt;
| &lt;field-access&gt;
| &lt;comparison&gt;
| &lt;logical&gt;
| &lt;unary&gt;
| &lt;quantifier&gt;
| "(" &lt;expression&gt; ")"
&lt;literal&gt; ::= &lt;int&gt; | &lt;float&gt; | &lt;string&gt; | &lt;bool&gt;
&lt;identifier&gt; ::= &lt;simple-name&gt;
| &lt;qualified-path&gt;
&lt;field-access&gt; ::= &lt;expression&gt; "." &lt;identifier&gt;
| "self" "." &lt;identifier&gt;
| "other" "." &lt;identifier&gt;
&lt;comparison&gt; ::= &lt;expression&gt; &lt;comp-op&gt; &lt;expression&gt;
&lt;comp-op&gt; ::= "==" | "!=" | "&lt;" | "&lt;=" | "&gt;" | "&gt;="
&lt;logical&gt; ::= &lt;expression&gt; "and" &lt;expression&gt;
| &lt;expression&gt; "or" &lt;expression&gt;
&lt;unary&gt; ::= "not" &lt;expression&gt;
| "-" &lt;expression&gt;
&lt;quantifier&gt; ::= ("forall" | "exists") &lt;identifier&gt; "in" &lt;expression&gt; ":" &lt;expression&gt;
</code></pre>
<h2 id="literals"><a class="header" href="#literals">Literals</a></h2>
<h3 id="integer-literals"><a class="header" href="#integer-literals">Integer Literals</a></h3>
<pre><code class="language-storybook">42
-7
0
1000
</code></pre>
<h3 id="float-literals"><a class="header" href="#float-literals">Float Literals</a></h3>
<pre><code class="language-storybook">3.14
-0.5
0.0
100.25
</code></pre>
<h3 id="string-literals"><a class="header" href="#string-literals">String Literals</a></h3>
<pre><code class="language-storybook">"Martha"
"Sourdough takes patience."
"active"
</code></pre>
<p>Strings are enclosed in double quotes. Escape sequences: <code>\n</code>, <code>\t</code>, <code>\\</code>, <code>\"</code>.</p>
<h3 id="boolean-literals"><a class="header" href="#boolean-literals">Boolean Literals</a></h3>
<pre><code class="language-storybook">true
false
</code></pre>
<h2 id="identifiers"><a class="header" href="#identifiers">Identifiers</a></h2>
<p>Identifiers reference fields or entities.</p>
<h3 id="simple-identifiers"><a class="header" href="#simple-identifiers">Simple Identifiers</a></h3>
<pre><code class="language-storybook">health
enemy_count
is_ready
</code></pre>
<h3 id="qualified-paths"><a class="header" href="#qualified-paths">Qualified Paths</a></h3>
<pre><code class="language-storybook">Martha.skill_level
Character.emotional_state
</code></pre>
<h2 id="comparison-operators"><a class="header" href="#comparison-operators">Comparison Operators</a></h2>
<h3 id="equality-"><a class="header" href="#equality-">Equality: <code>==</code></a></h3>
<p>Tests if two values are equal.</p>
<pre><code class="language-storybook">name == "Martha"
count == 5
status == active
</code></pre>
<p><strong>Type compatibility:</strong></p>
<ul>
<li>Both operands must be the same type</li>
<li>Works with: int, float, string, bool, enum values</li>
</ul>
<h3 id="inequality-"><a class="header" href="#inequality-">Inequality: <code>!=</code></a></h3>
<p>Tests if two values are not equal.</p>
<pre><code class="language-storybook">name != "Gregory"
health != 0
ready != true
</code></pre>
<h3 id="less-than"><a class="header" href="#less-than">Less Than: <code>&lt;</code></a></h3>
<p>Tests if left operand is less than right.</p>
<pre><code class="language-storybook">health &lt; 20
age &lt; 18
distance &lt; 10.0
</code></pre>
<p><strong>Valid types:</strong> int, float</p>
<h3 id="less-than-or-equal-"><a class="header" href="#less-than-or-equal-">Less Than or Equal: <code>&lt;=</code></a></h3>
<pre><code class="language-storybook">health &lt;= 50
count &lt;= max_count
</code></pre>
<h3 id="greater-than"><a class="header" href="#greater-than">Greater Than: <code>&gt;</code></a></h3>
<pre><code class="language-storybook">strength &gt; 10
bond &gt; 0.5
</code></pre>
<h3 id="greater-than-or-equal-"><a class="header" href="#greater-than-or-equal-">Greater Than or Equal: <code>&gt;=</code></a></h3>
<pre><code class="language-storybook">age &gt;= 21
score &gt;= 100
</code></pre>
<h2 id="logical-operators"><a class="header" href="#logical-operators">Logical Operators</a></h2>
<h3 id="and-and"><a class="header" href="#and-and">AND: <code>and</code></a></h3>
<p>Both operands must be true.</p>
<pre><code class="language-storybook">health &lt; 50 and has_potion
is_ready and not is_busy
age &gt;= 18 and age &lt; 65
</code></pre>
<p><strong>Evaluation:</strong> Short-circuit (if left is false, right is not evaluated)</p>
<h3 id="or-or"><a class="header" href="#or-or">OR: <code>or</code></a></h3>
<p>At least one operand must be true.</p>
<pre><code class="language-storybook">is_day or is_lit
health &lt; 20 or surrounded
enemy_count == 0 or all_enemies_dead
</code></pre>
<p><strong>Evaluation:</strong> Short-circuit (if left is true, right is not evaluated)</p>
<h3 id="operator-precedence"><a class="header" href="#operator-precedence">Operator Precedence</a></h3>
<p>From highest to lowest:</p>
<ol>
<li><strong>Parentheses</strong>: <code>(...)</code></li>
<li><strong>Unary</strong>: <code>not</code>, <code>-</code></li>
<li><strong>Comparisons</strong>: <code>==</code>, <code>!=</code>, <code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code></li>
<li><strong>AND</strong>: <code>and</code></li>
<li><strong>OR</strong>: <code>or</code></li>
</ol>
<p><strong>Examples:</strong></p>
<pre><code class="language-storybook">not is_ready and is_awake
// Equivalent to: (not is_ready) and is_awake
health &lt; 50 or is_poisoned and has_antidote
// Equivalent to: (health &lt; 50) or (is_poisoned and has_antidote)
// Use parentheses for clarity:
(health &lt; 50 or is_poisoned) and has_antidote
</code></pre>
<h2 id="unary-operators"><a class="header" href="#unary-operators">Unary Operators</a></h2>
<h3 id="not-not"><a class="header" href="#not-not">NOT: <code>not</code></a></h3>
<p>Inverts a boolean value.</p>
<pre><code class="language-storybook">not is_ready
not (health &lt; 20)
not enemy_nearby and safe
</code></pre>
<h3 id="negation--"><a class="header" href="#negation--">Negation: <code>-</code></a></h3>
<p>Negates a numeric value.</p>
<pre><code class="language-storybook">-health
-10
-(max_value - current_value)
</code></pre>
<h2 id="field-access"><a class="header" href="#field-access">Field Access</a></h2>
<h3 id="direct-field-access"><a class="header" href="#direct-field-access">Direct Field Access</a></h3>
<pre><code class="language-storybook">health
bond
emotional_state
</code></pre>
<p>References a field on the current entity.</p>
<h3 id="dot-access"><a class="header" href="#dot-access">Dot Access</a></h3>
<pre><code class="language-storybook">Martha.skill_level
Character.emotional_state
enemy.health
</code></pre>
<p>Access fields on other entities.</p>
<h3 id="self-access"><a class="header" href="#self-access">Self Access</a></h3>
<p>In relationships and certain contexts, <code>self</code> refers to the current participant:</p>
<pre><code class="language-storybook">self.bond
self.responsibility
self.trust
</code></pre>
<p><strong>Use case:</strong> Relationship transitions, symmetric queries</p>
<h3 id="other-access"><a class="header" href="#other-access">Other Access</a></h3>
<p>In relationships, <code>other</code> refers to other participants:</p>
<pre><code class="language-storybook">other.bond
other.aware_of_mentor
other.respect
</code></pre>
<p><strong>Use case:</strong> Relationship queries with perspective</p>
<h3 id="example-in-life-arcs"><a class="header" href="#example-in-life-arcs">Example in Life Arcs</a></h3>
<pre><code class="language-storybook">life_arc RelationshipState {
state new {
on self.bond &gt; 0.7 and other.bond &gt; 0.7 -&gt; stable
}
state stable {
on self.bond &lt; 0.3 -&gt; troubled
on other.bond &lt; 0.3 -&gt; troubled
}
state troubled {
on self.bond &lt; 0.1 or other.bond &lt; 0.1 -&gt; broken
}
state broken {}
}
</code></pre>
<h2 id="quantifiers"><a class="header" href="#quantifiers">Quantifiers</a></h2>
<p>Quantifiers test conditions over collections.</p>
<h3 id="forall-forall"><a class="header" href="#forall-forall">ForAll: <code>forall</code></a></h3>
<p>Tests if a condition holds for all elements in a collection.</p>
<pre><code class="language-storybook">forall e in enemies: e.defeated
forall item in inventory: item.weight &lt; 10
</code></pre>
<p><strong>Syntax:</strong></p>
<pre><code class="language-bnf">forall &lt;variable&gt; in &lt;collection&gt;: &lt;predicate&gt;
</code></pre>
<p><strong>Semantics:</strong></p>
<ul>
<li>Returns <code>true</code> if predicate is true for every element</li>
<li>Returns <code>true</code> for empty collections (vacuously true)</li>
</ul>
<p><strong>Examples:</strong></p>
<pre><code class="language-storybook">// All enemies defeated?
forall enemy in enemies: enemy.health &lt;= 0
// All party members ready?
forall member in party: member.is_ready
// All doors locked?
forall door in doors: door.is_locked
</code></pre>
<h3 id="exists-exists"><a class="header" href="#exists-exists">Exists: <code>exists</code></a></h3>
<p>Tests if a condition holds for at least one element.</p>
<pre><code class="language-storybook">exists e in enemies: e.is_hostile
exists item in inventory: item.is_healing_potion
</code></pre>
<p><strong>Syntax:</strong></p>
<pre><code class="language-bnf">exists &lt;variable&gt; in &lt;collection&gt;: &lt;predicate&gt;
</code></pre>
<p><strong>Semantics:</strong></p>
<ul>
<li>Returns <code>true</code> if predicate is true for any element</li>
<li>Returns <code>false</code> for empty collections</li>
</ul>
<p><strong>Examples:</strong></p>
<pre><code class="language-storybook">// Any enemy nearby?
exists enemy in enemies: enemy.distance &lt; 10
// Any door unlocked?
exists door in doors: not door.is_locked
// Any ally wounded?
exists ally in allies: ally.health &lt; ally.max_health * 0.5
</code></pre>
<h3 id="nested-quantifiers"><a class="header" href="#nested-quantifiers">Nested Quantifiers</a></h3>
<p>Quantifiers can nest:</p>
<pre><code class="language-storybook">forall team in teams: exists player in team: player.is_leader
// Every team has at least one leader
exists room in dungeon: forall enemy in room.enemies: enemy.defeated
// At least one room has all enemies defeated
</code></pre>
<h2 id="usage-in-context"><a class="header" href="#usage-in-context">Usage in Context</a></h2>
<h3 id="life-arc-transitions"><a class="header" href="#life-arc-transitions">Life Arc Transitions</a></h3>
<pre><code class="language-storybook">life_arc CombatState {
state idle {
on enemy_count &gt; 0 -&gt; combat
}
state combat {
on health &lt; 20 -&gt; fleeing
on enemy_count == 0 -&gt; victorious
}
state fleeing {
on distance_from_enemies &gt; 100 -&gt; safe
}
state victorious {
on celebration_complete -&gt; idle
}
state safe {
on health &gt;= 50 -&gt; idle
}
}
</code></pre>
<h3 id="behavior-tree-conditions"><a class="header" href="#behavior-tree-conditions">Behavior Tree Conditions</a></h3>
<pre><code class="language-storybook">behavior GuardedAction {
if(health &gt; 50 and has_weapon) {
AggressiveAttack
}
}
behavior ConditionalChoice {
choose tactics {
then melee {
if(distance &lt; 5 and weapon_type == "sword")
MeleeAttack
}
then ranged {
if(distance &gt;= 5 and has_arrows)
RangedAttack
}
}
}
</code></pre>
<h3 id="behavior-tree-conditions-1"><a class="header" href="#behavior-tree-conditions-1">Behavior Tree Conditions</a></h3>
<pre><code class="language-storybook">behavior SmartAI {
choose strategy {
then aggressive {
if(health &gt; 70 and enemy_count &lt; 3)
Attack
}
then defensive {
if(health &lt; 30 or enemy_count &gt;= 5)
Defend
}
then balanced {
if(health &gt;= 30 and health &lt;= 70)
TacticalManeuver
}
}
}
</code></pre>
<h2 id="type-system"><a class="header" href="#type-system">Type System</a></h2>
<h3 id="type-compatibility"><a class="header" href="#type-compatibility">Type Compatibility</a></h3>
<p>Comparisons require compatible types:</p>
<div class="table-wrapper"><table><thead><tr><th>Operator</th><th>Left Type</th><th>Right Type</th><th>Valid?</th></tr></thead><tbody>
<tr><td><code>==</code>, <code>!=</code></td><td>int</td><td>int</td><td></td></tr>
<tr><td><code>==</code>, <code>!=</code></td><td>float</td><td>float</td><td></td></tr>
<tr><td><code>==</code>, <code>!=</code></td><td>string</td><td>string</td><td></td></tr>
<tr><td><code>==</code>, <code>!=</code></td><td>bool</td><td>bool</td><td></td></tr>
<tr><td><code>==</code>, <code>!=</code></td><td>enum</td><td>same enum</td><td></td></tr>
<tr><td><code>==</code>, <code>!=</code></td><td>int</td><td>float</td><td></td></tr>
<tr><td><code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code></td><td>int</td><td>int</td><td></td></tr>
<tr><td><code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code></td><td>float</td><td>float</td><td></td></tr>
<tr><td><code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code></td><td>string</td><td>string</td><td></td></tr>
</tbody></table>
</div>
<h3 id="implicit-coercion"><a class="header" href="#implicit-coercion">Implicit Coercion</a></h3>
<p><strong>None.</strong> Storybook has no implicit type coercion. All comparisons must be between compatible types.</p>
<p><strong>Error:</strong></p>
<pre><code class="language-storybook">count == "5" // Error: int vs string
health &lt; true // Error: int vs bool
</code></pre>
<p><strong>Correct:</strong></p>
<pre><code class="language-storybook">count == 5
health &lt; 50
</code></pre>
<h2 id="special-keyword-is"><a class="header" href="#special-keyword-is">Special Keyword: <code>is</code></a></h2>
<p>The <code>is</code> keyword provides syntactic sugar for equality with enum values:</p>
<pre><code class="language-storybook">// Instead of:
status == active
// You can write:
status is active
</code></pre>
<p><strong>More examples:</strong></p>
<pre><code class="language-storybook">name is "Martha"
skill_level is master
emotional_state is focused
</code></pre>
<p>This is purely syntactic—<code>is</code> and <code>==</code> are equivalent.</p>
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
<h3 id="simple-conditions"><a class="header" href="#simple-conditions">Simple Conditions</a></h3>
<pre><code class="language-storybook">health &lt; 20
enemy_nearby
not is_ready
count &gt; 5
</code></pre>
<h3 id="complex-conditions"><a class="header" href="#complex-conditions">Complex Conditions</a></h3>
<pre><code class="language-storybook">(health &lt; 20 and not has_potion) or surrounded
forall e in enemies: e.defeated
exists item in inventory: item.is_healing_potion and item.quantity &gt; 0
</code></pre>
<h3 id="life-arc-with-complex-conditions"><a class="header" href="#life-arc-with-complex-conditions">Life Arc with Complex Conditions</a></h3>
<pre><code class="language-storybook">life_arc CharacterMood {
state content {
on health &lt; 30 or hunger &gt; 80 -&gt; distressed
on social_interaction &gt; 0.8 -&gt; happy
}
state distressed {
on health &gt;= 50 and hunger &lt; 30 -&gt; content
on (health &lt; 10 or hunger &gt; 95) and help_available -&gt; desperate
}
state happy {
on social_interaction &lt; 0.3 -&gt; content
on received_bad_news -&gt; distressed
}
state desperate {
on help_received -&gt; distressed
}
}
</code></pre>
<h3 id="behavior-with-quantifiers"><a class="header" href="#behavior-with-quantifiers">Behavior with Quantifiers</a></h3>
<pre><code class="language-storybook">behavior SquadLeader {
choose leadership {
then regroup {
if(squad_has_wounded)
OrderRetreat
}
then advance {
if(squad_all_ready)
OrderAdvance
}
then hold_position {
if(not squad_all_ready)
OrderHold
}
}
}
</code></pre>
<h3 id="relationship-query"><a class="header" href="#relationship-query">Relationship Query</a></h3>
<pre><code class="language-storybook">life_arc FriendshipQuality {
state new_friends {
on self.bond &gt; 0.7 and other.bond &gt; 0.7 -&gt; strong_bond
on self.trust &lt; 0.3 or other.trust &lt; 0.3 -&gt; shaky
}
state strong_bond {
on self.bond &lt; 0.5 -&gt; weakening
}
state weakening {
on self.bond &lt; 0.2 or other.bond &lt; 0.2 -&gt; ended
on self.bond &gt; 0.7 and other.bond &gt; 0.7 -&gt; strong_bond
}
state shaky {
on self.trust &gt; 0.6 and other.trust &gt; 0.6 -&gt; new_friends
on self.trust &lt; 0.1 or other.trust &lt; 0.1 -&gt; ended
}
state ended {}
}
</code></pre>
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
<ol>
<li><strong>Type consistency</strong>: Both sides of comparison must be compatible types</li>
<li><strong>Boolean context</strong>: Logical operators (<code>and</code>, <code>or</code>, <code>not</code>) require boolean operands</li>
<li><strong>Field existence</strong>: Referenced fields must exist on the entity</li>
<li><strong>Collection validity</strong>: Quantifiers require collection-typed expressions</li>
<li><strong>Variable scope</strong>: Quantifier variables only valid within their predicate</li>
<li><strong>No division by zero</strong>: Arithmetic operations must not divide by zero</li>
<li><strong>Enum validity</strong>: Enum comparisons must reference defined enum values</li>
</ol>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-use-parentheses-for-clarity"><a class="header" href="#1-use-parentheses-for-clarity">1. Use Parentheses for Clarity</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">health &lt; 50 or is_poisoned and has_antidote
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">(health &lt; 50 or is_poisoned) and has_antidote
</code></pre>
<h3 id="2-break-complex-conditions"><a class="header" href="#2-break-complex-conditions">2. Break Complex Conditions</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">on (health &lt; 20 and not has_potion) or (surrounded and not has_escape) or (enemy_count &gt; 10 and weapon_broken) -&gt; desperate
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">state combat {
on health &lt; 20 and not has_potion -&gt; desperate
on surrounded and not has_escape -&gt; desperate
on enemy_count &gt; 10 and weapon_broken -&gt; desperate
}
</code></pre>
<h3 id="3-name-complex-conditions"><a class="header" href="#3-name-complex-conditions">3. Name Complex Conditions</a></h3>
<p>For repeated complex conditions, consider using intermediate fields:</p>
<p><strong>Instead of:</strong></p>
<pre><code class="language-storybook">on health &lt; (max_health * 0.2) and enemy_count &gt; 5 -&gt; flee
</code></pre>
<p><strong>Consider:</strong></p>
<pre><code class="language-storybook">// In character definition:
critically_wounded: health &lt; (max_health * 0.2)
outnumbered: enemy_count &gt; 5
// In life arc:
on critically_wounded and outnumbered -&gt; flee
</code></pre>
<h3 id="4-use-is-for-enums"><a class="header" href="#4-use-is-for-enums">4. Use <code>is</code> for Enums</a></h3>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">status is active
emotional_state is focused
</code></pre>
<p><strong>Over:</strong></p>
<pre><code class="language-storybook">status == active
emotional_state == focused
</code></pre>
<h3 id="5-quantifiers-for-collections"><a class="header" href="#5-quantifiers-for-collections">5. Quantifiers for Collections</a></h3>
<p><strong>Avoid:</strong></p>
<pre><code class="language-storybook">// Manual checks for each element
if enemy1.defeated and enemy2.defeated and enemy3.defeated
</code></pre>
<p><strong>Prefer:</strong></p>
<pre><code class="language-storybook">if forall enemy in enemies: enemy.defeated
</code></pre>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./13-life-arcs.html">Life Arcs</a> - Transition conditions</li>
<li><a href="./11-behavior-trees.html">Behavior Trees</a> - Guard and condition nodes</li>
<li><a href="./12-decorators.html">Decorators</a> - Guard decorator</li>
<li><a href="./15-relationships.html">Relationships</a> - Self/other field access</li>
<li><a href="./18-value-types.html">Value Types</a> - Literal value types</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Type safety</strong>: Strong typing prevents type errors at compile time</li>
<li><strong>Short-circuit evaluation</strong>: AND/OR operators optimize evaluation</li>
<li><strong>Quantifiers</strong>: Enable expressive collection queries</li>
<li><strong>Field access</strong>: Context-sensitive (<code>self</code>, <code>other</code>) for relationships</li>
<li><strong>Boolean algebra</strong>: Standard logical operators with expected semantics</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/16-other-declarations.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/18-value-types.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../reference/16-other-declarations.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/18-value-types.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,857 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Value Types - 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="values"><a class="header" href="#values">Values</a></h1>
<p>Values are the fundamental data types in Storybook. Every field in a character, template, or other declaration contains a value. This chapter provides a complete reference for all supported value types.</p>
<h2 id="value-types-overview"><a class="header" href="#value-types-overview">Value Types Overview</a></h2>
<p>Storybook supports 12 value types:</p>
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Example</th><th>Use Case</th></tr></thead><tbody>
<tr><td><strong>Int</strong></td><td><code>42</code>, <code>-7</code></td><td>Quantities, IDs, counts</td></tr>
<tr><td><strong>Float</strong></td><td><code>3.14</code>, <code>-0.5</code></td><td>Measurements, probabilities</td></tr>
<tr><td><strong>String</strong></td><td><code>"Hello"</code>, <code>"Martha"</code></td><td>Text, names, descriptions</td></tr>
<tr><td><strong>Bool</strong></td><td><code>true</code>, <code>false</code></td><td>Flags, switches</td></tr>
<tr><td><strong>Time</strong></td><td><code>14:30</code>, <code>09:15:30</code></td><td>Clock times, schedule blocks</td></tr>
<tr><td><strong>Duration</strong></td><td><code>2h30m</code>, <code>45s</code></td><td>Time intervals</td></tr>
<tr><td><strong>Range</strong></td><td><code>20..40</code>, <code>0.5..1.0</code></td><td>Template variation bounds</td></tr>
<tr><td><strong>Identifier</strong></td><td><code>Martha</code>, <code>items::sword</code></td><td>References to other declarations</td></tr>
<tr><td><strong>List</strong></td><td><code>[1, 2, 3]</code>, <code>["a", "b"]</code></td><td>Collections</td></tr>
<tr><td><strong>Object</strong></td><td><code>{x: 10, y: 20}</code></td><td>Structured data</td></tr>
<tr><td><strong>ProseBlock</strong></td><td><code>---tag content ---</code></td><td>Long-form narrative</td></tr>
<tr><td><strong>Override</strong></td><td><code>TemplateX with {...}</code></td><td>Template instantiation with modifications</td></tr>
</tbody></table>
</div>
<h2 id="integer"><a class="header" href="#integer">Integer</a></h2>
<p>Signed 64-bit integers.</p>
<h3 id="syntax"><a class="header" href="#syntax">Syntax</a></h3>
<pre><code class="language-bnf">&lt;int&gt; ::= ["-"] &lt;digit&gt;+
&lt;digit&gt; ::= "0".."9"
</code></pre>
<h3 id="examples"><a class="header" href="#examples">Examples</a></h3>
<pre><code class="language-storybook">character Hero {
age: 25
gold: 1500
reputation: -10
}
</code></pre>
<h3 id="range"><a class="header" href="#range">Range</a></h3>
<ul>
<li>Minimum: <code>-9,223,372,036,854,775,808</code></li>
<li>Maximum: <code>9,223,372,036,854,775,807</code></li>
</ul>
<h3 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h3>
<ul>
<li>Counts (items, population)</li>
<li>Identifiers (IDs, keys)</li>
<li>Scores (reputation, alignment)</li>
<li>Whole quantities (gold pieces, HP)</li>
</ul>
<h2 id="float"><a class="header" href="#float">Float</a></h2>
<p>64-bit floating-point numbers (IEEE 754 double precision).</p>
<h3 id="syntax-1"><a class="header" href="#syntax-1">Syntax</a></h3>
<pre><code class="language-bnf">&lt;float&gt; ::= ["-"] &lt;digit&gt;+ "." &lt;digit&gt;+
| ["-"] &lt;digit&gt;+ ("e" | "E") ["+"|"-"] &lt;digit&gt;+
</code></pre>
<h3 id="examples-1"><a class="header" href="#examples-1">Examples</a></h3>
<pre><code class="language-storybook">character Wizard {
mana: 100.0
spell_power: 1.5
corruption: 0.03
precision: 1e-6
}
</code></pre>
<h3 id="special-values"><a class="header" href="#special-values">Special Values</a></h3>
<ul>
<li>No <code>NaN</code> or <code>Infinity</code> support (use validation to prevent)</li>
<li>Negative zero (<code>-0.0</code>) equals positive zero (<code>0.0</code>)</li>
</ul>
<h3 id="use-cases-1"><a class="header" href="#use-cases-1">Use Cases</a></h3>
<ul>
<li>Probabilities (0.0 to 1.0)</li>
<li>Percentages (0.0 to 100.0)</li>
<li>Measurements with precision</li>
<li>Ratios and scaling factors</li>
</ul>
<h2 id="string"><a class="header" href="#string">String</a></h2>
<p>UTF-8 encoded text enclosed in double quotes.</p>
<h3 id="syntax-2"><a class="header" href="#syntax-2">Syntax</a></h3>
<pre><code class="language-bnf">&lt;string&gt; ::= '"' &lt;string-char&gt;* '"'
&lt;string-char&gt; ::= &lt;any-char-except-quote-or-backslash&gt;
| &lt;escape-sequence&gt;
&lt;escape-sequence&gt; ::= "\n" | "\r" | "\t" | "\\" | "\""
</code></pre>
<h3 id="escape-sequences"><a class="header" href="#escape-sequences">Escape Sequences</a></h3>
<ul>
<li><code>\n</code> - Newline</li>
<li><code>\r</code> - Carriage return</li>
<li><code>\t</code> - Tab</li>
<li><code>\\</code> - Backslash</li>
<li><code>\"</code> - Double quote</li>
</ul>
<h3 id="examples-2"><a class="header" href="#examples-2">Examples</a></h3>
<pre><code class="language-storybook">character Martha {
name: "Martha Baker"
greeting: "Fresh from the oven!"
multiline: "Line 1\nLine 2\nLine 3"
quote: "She said, \"The bread is ready!\""
}
</code></pre>
<h3 id="limitations"><a class="header" href="#limitations">Limitations</a></h3>
<ul>
<li>No raw strings (r“…“) or multi-line literals</li>
<li>For long text, use <a href="#prose-blocks">prose blocks</a></li>
<li>Maximum length: Implementation-defined (typically several MB)</li>
</ul>
<h3 id="use-cases-2"><a class="header" href="#use-cases-2">Use Cases</a></h3>
<ul>
<li>Character names</li>
<li>Short descriptions</li>
<li>Dialogue snippets</li>
<li>Enum-like values (before proper enums)</li>
</ul>
<h2 id="boolean"><a class="header" href="#boolean">Boolean</a></h2>
<p>Logical true/false values.</p>
<h3 id="syntax-3"><a class="header" href="#syntax-3">Syntax</a></h3>
<pre><code class="language-bnf">&lt;bool&gt; ::= "true" | "false"
</code></pre>
<h3 id="examples-3"><a class="header" href="#examples-3">Examples</a></h3>
<pre><code class="language-storybook">character Guard {
is_awake: true
has_seen_player: false
loyal_to_king: true
}
</code></pre>
<h3 id="use-cases-3"><a class="header" href="#use-cases-3">Use Cases</a></h3>
<ul>
<li>Feature flags (can_fly, is_hostile)</li>
<li>State tracking (door_open, quest_complete)</li>
<li>Conditions (is_friendly, accepts_bribes)</li>
</ul>
<h2 id="time"><a class="header" href="#time">Time</a></h2>
<p>Clock time in 24-hour format.</p>
<h3 id="syntax-4"><a class="header" href="#syntax-4">Syntax</a></h3>
<pre><code class="language-bnf">&lt;time&gt; ::= &lt;hour&gt; ":" &lt;minute&gt;
| &lt;hour&gt; ":" &lt;minute&gt; ":" &lt;second&gt;
&lt;hour&gt; ::= "0".."23"
&lt;minute&gt; ::= "0".."59"
&lt;second&gt; ::= "0".."59"
</code></pre>
<h3 id="examples-4"><a class="header" href="#examples-4">Examples</a></h3>
<pre><code class="language-storybook">schedule BakerySchedule {
block {
start: 06:00
end: 08:30
action: baking::prepare_dough
}
block {
start: 14:30:15 // With seconds
end: 15:00:00
action: baking::afternoon_cleanup
}
}
character EarlyRiser {
wake_time: 05:30
bedtime: 21:00
}
</code></pre>
<h3 id="validation"><a class="header" href="#validation">Validation</a></h3>
<ul>
<li>Hours: 0-23 (24-hour format)</li>
<li>Minutes: 0-59</li>
<li>Seconds: 0-59 (optional)</li>
<li>No AM/PM notation</li>
<li>No timezone support (context-dependent)</li>
</ul>
<h3 id="use-cases-4"><a class="header" href="#use-cases-4">Use Cases</a></h3>
<ul>
<li>Schedule blocks (see <a href="./16-schedules.html">Schedules</a>)</li>
<li>Character routines</li>
<li>Event timing</li>
<li>Time-based conditions</li>
</ul>
<h2 id="duration"><a class="header" href="#duration">Duration</a></h2>
<p>Time intervals with hour/minute/second components.</p>
<h3 id="syntax-5"><a class="header" href="#syntax-5">Syntax</a></h3>
<pre><code class="language-bnf">&lt;duration&gt; ::= &lt;duration-component&gt;+
&lt;duration-component&gt; ::= &lt;number&gt; ("h" | "m" | "s")
&lt;number&gt; ::= &lt;digit&gt;+
</code></pre>
<h3 id="examples-5"><a class="header" href="#examples-5">Examples</a></h3>
<pre><code class="language-storybook">character Traveler {
journey_time: 2h30m
rest_needed: 8h
sprint_duration: 45s
}
behavior TimedAction {
choose {
timeout(5m) {
CompleteObjective
}
cooldown(30s) {
UseSpecialAbility
}
}
}
</code></pre>
<h3 id="components"><a class="header" href="#components">Components</a></h3>
<ul>
<li><code>h</code> - Hours</li>
<li><code>m</code> - Minutes</li>
<li><code>s</code> - Seconds</li>
</ul>
<p>Can combine multiple components: <code>1h30m15s</code></p>
<h3 id="validation-1"><a class="header" href="#validation-1">Validation</a></h3>
<ul>
<li>All components non-negative</li>
<li>No fractional components (<code>1.5h</code> not allowed; use <code>1h30m</code>)</li>
<li>Can specify same unit multiple times: <code>90m</code> = <code>1h30m</code> (normalized)</li>
</ul>
<h3 id="use-cases-5"><a class="header" href="#use-cases-5">Use Cases</a></h3>
<ul>
<li>Behavior tree timeouts/cooldowns</li>
<li>Travel times</li>
<li>Cooldown periods</li>
<li>Event durations</li>
</ul>
<h2 id="range-1"><a class="header" href="#range-1">Range</a></h2>
<p>Numeric range with inclusive bounds (for templates only).</p>
<h3 id="syntax-6"><a class="header" href="#syntax-6">Syntax</a></h3>
<pre><code class="language-bnf">&lt;range&gt; ::= &lt;value&gt; ".." &lt;value&gt;
</code></pre>
<p>Both bounds must be the same type (both int or both float).</p>
<h3 id="examples-6"><a class="header" href="#examples-6">Examples</a></h3>
<pre><code class="language-storybook">template Villager {
age: 18..65
wealth: 10..100
height: 150.0..190.0 // Float range
}
template RandomEvent {
probability: 0.0..1.0
damage: 5..20
}
</code></pre>
<h3 id="semantics"><a class="header" href="#semantics">Semantics</a></h3>
<ul>
<li><strong>Templates only</strong>: Ranges are only valid in templates</li>
<li><strong>Instantiation</strong>: When a template is used, a specific value within the range is selected</li>
<li><strong>Inclusive bounds</strong>: Both <code>min</code> and <code>max</code> are included</li>
<li><strong>Order matters</strong>: <code>min</code> must be ≤ <code>max</code></li>
</ul>
<h3 id="validation-2"><a class="header" href="#validation-2">Validation</a></h3>
<ul>
<li>Both bounds same type</li>
<li><code>min</code><code>max</code></li>
<li>Cannot use ranges in character/species/etc. (only templates)</li>
</ul>
<h3 id="use-cases-6"><a class="header" href="#use-cases-6">Use Cases</a></h3>
<ul>
<li>Template variation</li>
<li>Procedural generation</li>
<li>Random NPC attributes</li>
<li>Loot table ranges</li>
</ul>
<h2 id="identifier"><a class="header" href="#identifier">Identifier</a></h2>
<p>Reference to another declaration by qualified path.</p>
<h3 id="syntax-7"><a class="header" href="#syntax-7">Syntax</a></h3>
<pre><code class="language-bnf">&lt;identifier&gt; ::= &lt;simple-name&gt;
| &lt;qualified-path&gt;
&lt;simple-name&gt; ::= &lt;ident&gt;
&lt;qualified-path&gt; ::= &lt;ident&gt; ("::" &lt;ident&gt;)+
</code></pre>
<h3 id="examples-7"><a class="header" href="#examples-7">Examples</a></h3>
<pre><code class="language-storybook">character Martha: Human { // Species reference
// ...
}
character Elena from Apprentice { // Template reference
// ...
}
character Guard {
uses behaviors: [
{ tree: guards::patrol } // Behavior reference
]
uses schedule: DailyRoutine // Schedule reference
}
relationship Partnership {
Martha // Character reference
Jane // Character reference
}
</code></pre>
<h3 id="resolution"><a class="header" href="#resolution">Resolution</a></h3>
<ul>
<li><strong>Unqualified</strong>: Searches current module, then imports</li>
<li><strong>Qualified</strong>: <code>module::submodule::Name</code></li>
<li><strong>Validation</strong>: Compiler ensures all references resolve</li>
</ul>
<h3 id="use-cases-7"><a class="header" href="#use-cases-7">Use Cases</a></h3>
<ul>
<li>Species typing (<code>: Species</code>)</li>
<li>Template inheritance (<code>from Template</code>)</li>
<li>Behavior tree references</li>
<li>Schedule references</li>
<li>Relationship participants</li>
<li>Cross-references</li>
</ul>
<h2 id="list"><a class="header" href="#list">List</a></h2>
<p>Ordered collection of values.</p>
<h3 id="syntax-8"><a class="header" href="#syntax-8">Syntax</a></h3>
<pre><code class="language-bnf">&lt;list&gt; ::= "[" "]"
| "[" &lt;value&gt; ("," &lt;value&gt;)* "]"
</code></pre>
<h3 id="examples-8"><a class="header" href="#examples-8">Examples</a></h3>
<pre><code class="language-storybook">character Mage {
known_spells: ["Fireball", "Lightning", "Shield"]
spell_levels: [3, 5, 2]
party_members: [Martha, Jane, Elena]
}
character Traveler {
inventory: [
{item: "Sword", damage: 10},
{item: "Potion", healing: 50},
{item: "Key", opens: "TowerDoor"}
]
}
</code></pre>
<h3 id="type-constraints"><a class="header" href="#type-constraints">Type Constraints</a></h3>
<ul>
<li><strong>Homogeneous preferred</strong>: All elements should be the same type</li>
<li><strong>Heterogeneous allowed</strong>: Mixed types permitted but discouraged</li>
<li><strong>Empty lists</strong>: <code>[]</code> is valid</li>
</ul>
<h3 id="operations"><a class="header" href="#operations">Operations</a></h3>
<p>Lists are immutable at declaration time. Runtime list operations depend on the execution environment.</p>
<h3 id="use-cases-8"><a class="header" href="#use-cases-8">Use Cases</a></h3>
<ul>
<li>Collections (inventory, spells, party members)</li>
<li>References (multiple behaviors, schedules, participants)</li>
<li>Enum-like sets (before proper enums)</li>
</ul>
<h2 id="object"><a class="header" href="#object">Object</a></h2>
<p>Structured data with named fields.</p>
<h3 id="syntax-9"><a class="header" href="#syntax-9">Syntax</a></h3>
<pre><code class="language-bnf">&lt;object&gt; ::= "{" "}"
| "{" &lt;field&gt; ("," &lt;field&gt;)* "}"
&lt;field&gt; ::= &lt;identifier&gt; ":" &lt;value&gt;
</code></pre>
<h3 id="examples-9"><a class="header" href="#examples-9">Examples</a></h3>
<pre><code class="language-storybook">character Hero {
position: {x: 100, y: 200}
stats: {
strength: 15,
dexterity: 12,
intelligence: 10
}
equipment: {
weapon: {
name: "Longsword",
damage: 10,
enchantment: "Fire"
},
armor: {
name: "Plate Mail",
defense: 8
}
}
}
</code></pre>
<h3 id="nesting"><a class="header" href="#nesting">Nesting</a></h3>
<p>Objects can be nested arbitrarily deep:</p>
<pre><code class="language-storybook">character Complex {
deep: {
level1: {
level2: {
level3: {
value: 42
}
}
}
}
}
</code></pre>
<h3 id="use-cases-9"><a class="header" href="#use-cases-9">Use Cases</a></h3>
<ul>
<li>Structured data (position, stats)</li>
<li>Nested configurations</li>
<li>Inline data structures</li>
<li>Complex field values</li>
</ul>
<h2 id="prose-blocks"><a class="header" href="#prose-blocks">Prose Blocks</a></h2>
<p>Long-form narrative text with semantic tags.</p>
<h3 id="syntax-10"><a class="header" href="#syntax-10">Syntax</a></h3>
<pre><code class="language-bnf">&lt;prose-block&gt; ::= "---" &lt;tag&gt; &lt;content&gt; "---"
&lt;tag&gt; ::= &lt;identifier&gt;
&lt;content&gt; ::= &lt;any-text-except-triple-dash&gt;
</code></pre>
<h3 id="common-tags"><a class="header" href="#common-tags">Common Tags</a></h3>
<ul>
<li><code>---description</code>: General description</li>
<li><code>---backstory</code>: Character history</li>
<li><code>---appearance</code>: Physical description</li>
<li><code>---personality</code>: Behavioral traits</li>
<li><code>---motivation</code>: Goals and desires</li>
<li><code>---notes</code>: Meta-commentary</li>
<li><code>---ecology</code>: Species habitat/behavior</li>
<li><code>---culture</code>: Social structures</li>
<li><code>---narrative</code>: Story context</li>
</ul>
<h3 id="examples-10"><a class="header" href="#examples-10">Examples</a></h3>
<pre><code class="language-storybook">character Martha {
age: 34
---backstory
Martha learned to bake from her grandmother, starting at age
twelve. She now runs the most popular bakery in town, known
for her sourdough bread and unwavering quality standards.
---
---personality
Meticulous and patient, with an unwavering commitment to
quality. Tough but fair with her staff, and deeply loyal
to the customers who have supported her bakery for years.
---
}
species Dragon {
lifespan: 1000
---ecology
Dragons nest in high mountain caves and emerge every few decades
to hunt large prey. They hoard treasure as a mating display.
---
}
</code></pre>
<h3 id="formatting"><a class="header" href="#formatting">Formatting</a></h3>
<ul>
<li>Leading/trailing whitespace is preserved</li>
<li>No escape sequences needed</li>
<li>Can contain any characters except <code>---</code> on its own line</li>
<li>Indentation is significant for readability but not semantics</li>
</ul>
<h3 id="multiple-prose-blocks"><a class="header" href="#multiple-prose-blocks">Multiple Prose Blocks</a></h3>
<p>A single declaration can have multiple prose blocks with different tags:</p>
<pre><code class="language-storybook">character Gandalf {
---appearance
An old man with a long gray beard, pointed hat, and staff.
---
---backstory
One of the Istari sent to Middle-earth to contest Sauron.
---
---personality
Patient and wise, but with a mischievous streak.
---
}
</code></pre>
<h3 id="use-cases-10"><a class="header" href="#use-cases-10">Use Cases</a></h3>
<ul>
<li>Character backstories</li>
<li>Species descriptions</li>
<li>World-building flavor text</li>
<li>Design notes</li>
<li>Narrative context</li>
</ul>
<h2 id="override"><a class="header" href="#override">Override</a></h2>
<p>Template instantiation with field modifications.</p>
<h3 id="syntax-11"><a class="header" href="#syntax-11">Syntax</a></h3>
<pre><code class="language-bnf">&lt;override&gt; ::= &lt;qualified-path&gt; "with" "{" &lt;override-op&gt;* "}"
&lt;override-op&gt; ::= &lt;identifier&gt; ":" &lt;value&gt; // Set
| "remove" &lt;identifier&gt; // Remove
| "append" &lt;identifier&gt; ":" &lt;value&gt; // Append (for lists)
</code></pre>
<h3 id="examples-11"><a class="header" href="#examples-11">Examples</a></h3>
<h4 id="basic-override"><a class="header" href="#basic-override">Basic Override</a></h4>
<pre><code class="language-storybook">template BaseWarrior {
strength: 10
dexterity: 8
weapon: "Sword"
}
character StrongWarrior {
stats: BaseWarrior with {
strength: 15 // Override strength
}
}
</code></pre>
<h4 id="remove-fields"><a class="header" href="#remove-fields">Remove Fields</a></h4>
<pre><code class="language-storybook">template FullEquipment {
helmet: "Iron"
chest: "Plate"
legs: "Mail"
boots: "Leather"
}
character LightFighter {
equipment: FullEquipment with {
remove helmet
remove chest
}
}
</code></pre>
<h4 id="append-to-lists"><a class="header" href="#append-to-lists">Append to Lists</a></h4>
<pre><code class="language-storybook">template BasicSpells {
spells: ["Fireball", "Shield"]
}
character AdvancedMage {
magic: BasicSpells with {
append spells: "Teleport"
append spells: "Lightning"
}
// Result: ["Fireball", "Shield", "Teleport", "Lightning"]
}
</code></pre>
<h4 id="complex-override"><a class="header" href="#complex-override">Complex Override</a></h4>
<pre><code class="language-storybook">template RogueTemplate {
stealth: 15
lockpicking: 12
backstab_damage: 20
equipment: ["Dagger", "Lockpicks"]
}
character MasterThief {
abilities: RogueTemplate with {
stealth: 20 // Set
remove backstab_damage // Remove
append equipment: "Grappling Hook" // Append
}
}
</code></pre>
<h3 id="operations-1"><a class="header" href="#operations-1">Operations</a></h3>
<h4 id="set-default"><a class="header" href="#set-default"><code>set</code> (default)</a></h4>
<p>Replace a fields value:</p>
<pre><code class="language-storybook">field_name: new_value
</code></pre>
<h4 id="remove"><a class="header" href="#remove"><code>remove</code></a></h4>
<p>Delete a field from the template:</p>
<pre><code class="language-storybook">remove field_name
</code></pre>
<h4 id="append"><a class="header" href="#append"><code>append</code></a></h4>
<p>Add to a list field (field must be a list):</p>
<pre><code class="language-storybook">append field_name: value_to_add
</code></pre>
<h3 id="validation-3"><a class="header" href="#validation-3">Validation</a></h3>
<ul>
<li>Base template must exist</li>
<li>Overridden fields must exist in template</li>
<li>Removed fields must exist in template</li>
<li>Appended fields must be lists</li>
<li>Type compatibility enforced</li>
</ul>
<h3 id="use-cases-11"><a class="header" href="#use-cases-11">Use Cases</a></h3>
<ul>
<li>Customizing template instances</li>
<li>Procedural character generation</li>
<li>Variant creation</li>
<li>Data composition</li>
</ul>
<h2 id="type-coercion"><a class="header" href="#type-coercion">Type Coercion</a></h2>
<p>Storybook has <strong>no implicit type coercion</strong>. All type conversions must be explicit.</p>
<p><strong>Not allowed:</strong></p>
<pre><code class="language-storybook">character Wrong {
count: "42" // Error: expected int, got string
flag: 1 // Error: expected bool, got int
}
</code></pre>
<p><strong>Correct:</strong></p>
<pre><code class="language-storybook">character Right {
count: 42
flag: true
}
</code></pre>
<h2 id="null-and-optional"><a class="header" href="#null-and-optional">Null and Optional</a></h2>
<p>Storybook <strong>does not have <code>null</code></strong>. For optional values, use:</p>
<ol>
<li>
<p><strong>Template ranges</strong> with 0 as lower bound:</p>
<pre><code class="language-storybook">template MaybeWeapon {
weapon_count: 0..5
}
</code></pre>
</li>
<li>
<p><strong>Boolean flags</strong>:</p>
<pre><code class="language-storybook">character Guard {
has_weapon: false
}
</code></pre>
</li>
<li>
<p><strong>Empty lists</strong>:</p>
<pre><code class="language-storybook">character Unarmed {
weapons: []
}
</code></pre>
</li>
</ol>
<h2 id="summary-table"><a class="header" href="#summary-table">Summary Table</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Mutable?</th><th>Comparable?</th><th>Valid in Templates?</th><th>Notes</th></tr></thead><tbody>
<tr><td>Int</td><td>No</td><td>Yes</td><td>Yes</td><td>64-bit signed</td></tr>
<tr><td>Float</td><td>No</td><td>Yes</td><td>Yes</td><td>64-bit IEEE 754</td></tr>
<tr><td>String</td><td>No</td><td>Yes</td><td>Yes</td><td>UTF-8</td></tr>
<tr><td>Bool</td><td>No</td><td>Yes</td><td>Yes</td><td>true/false</td></tr>
<tr><td>Time</td><td>No</td><td>Yes</td><td>No</td><td>HH:MM or HH:MM:SS</td></tr>
<tr><td>Duration</td><td>No</td><td>Yes</td><td>No</td><td>Compounds (2h30m)</td></tr>
<tr><td>Range</td><td>No</td><td>No</td><td>Yes (only)</td><td>Template variation</td></tr>
<tr><td>Identifier</td><td>No</td><td>Yes</td><td>Yes</td><td>Declaration reference</td></tr>
<tr><td>List</td><td>No</td><td>Yes</td><td>Yes</td><td>Ordered collection</td></tr>
<tr><td>Object</td><td>No</td><td>Yes</td><td>Yes</td><td>Named fields</td></tr>
<tr><td>ProseBlock</td><td>No</td><td>No</td><td>Yes</td><td>Narrative text</td></tr>
<tr><td>Override</td><td>No</td><td>No</td><td>Yes</td><td>Template modification</td></tr>
</tbody></table>
</div>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./10-characters.html">Characters</a> - Using values in character fields</li>
<li><a href="./16-other-declarations.html#templates">Templates</a> - Range values and override syntax</li>
<li><a href="./14-schedules.html">Schedules</a> - Time and duration in schedules</li>
<li><a href="./11-behavior-trees.html">Behavior Trees</a> - Duration in decorators</li>
<li><a href="./17-expressions.html">Expressions</a> - Using values in conditions</li>
</ul>
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
<ul>
<li><strong>Immutability</strong>: All values are immutable at declaration time</li>
<li><strong>Type safety</strong>: Strong static typing with no implicit coercion</li>
<li><strong>Structural equivalence</strong>: Objects/lists compared by structure, not identity</li>
<li><strong>Prose as data</strong>: Narrative text is first-class data, not comments</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/17-expressions.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/19-validation.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../reference/17-expressions.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/19-validation.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,530 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Validation Rules - 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="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h1>
<p>The Storybook compiler performs multi-layered validation to catch errors before runtime. This chapter documents all validation rules, organized by declaration type, along with the error messages you can expect and how to fix them.</p>
<h2 id="validation-layers"><a class="header" href="#validation-layers">Validation Layers</a></h2>
<p>Storybook validation happens in four stages:</p>
<ol>
<li><strong>Lexical</strong>: Tokenization of raw text (invalid characters, malformed literals)</li>
<li><strong>Syntactic</strong>: Grammar structure (missing braces, wrong keyword order)</li>
<li><strong>Semantic</strong>: Cross-reference resolution, type checking, field merging</li>
<li><strong>Domain</strong>: Narrative-specific constraints (bond ranges, schedule overlaps)</li>
</ol>
<p>Errors at earlier stages prevent later stages from running.</p>
<h2 id="character-validation"><a class="header" href="#character-validation">Character Validation</a></h2>
<h3 id="required-rules"><a class="header" href="#required-rules">Required Rules</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Unique name</td><td>Character names must be unique within their module</td><td>Error</td></tr>
<tr><td>Species exists</td><td>If <code>: Species</code> is used, the species must be defined</td><td>Error</td></tr>
<tr><td>Templates exist</td><td>All templates in <code>from</code> clause must be defined</td><td>Error</td></tr>
<tr><td>No circular inheritance</td><td>Template chains cannot form cycles</td><td>Error</td></tr>
<tr><td>Field type consistency</td><td>Field values must match expected types</td><td>Error</td></tr>
<tr><td>Behavior trees exist</td><td>All <code>uses behaviors</code> references must resolve</td><td>Error</td></tr>
<tr><td>Schedules exist</td><td>All <code>uses schedule</code> references must resolve</td><td>Error</td></tr>
<tr><td>Prose tag uniqueness</td><td>Each prose tag can appear at most once per character</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples"><a class="header" href="#examples">Examples</a></h3>
<p><strong>Species not found:</strong></p>
<pre><code class="language-storybook">character Martha: Hobbit { // Error: species 'Hobbit' not defined
age: 34
}
</code></pre>
<p><strong>Fix:</strong> Define the species or correct the reference:</p>
<pre><code class="language-storybook">species Hobbit {
lifespan: 130
}
character Martha: Hobbit {
age: 34
}
</code></pre>
<p><strong>Duplicate character name:</strong></p>
<pre><code class="language-storybook">character Martha { age: 34 }
character Martha { age: 36 } // Error: duplicate character name 'Martha'
</code></pre>
<h2 id="template-validation"><a class="header" href="#template-validation">Template Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Unique name</td><td>Template names must be unique within their module</td><td>Error</td></tr>
<tr><td>Includes exist</td><td>All included templates must be defined</td><td>Error</td></tr>
<tr><td>No circular includes</td><td>Include chains cannot form cycles</td><td>Error</td></tr>
<tr><td>Range validity</td><td>Range bounds must satisfy min &lt;= max</td><td>Error</td></tr>
<tr><td>Range type match</td><td>Both bounds of a range must be the same type</td><td>Error</td></tr>
<tr><td>Strict enforcement</td><td>Characters using strict templates cannot add extra fields</td><td>Error</td></tr>
<tr><td>Resource links valid</td><td>Behavior/schedule references must resolve</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples-1"><a class="header" href="#examples-1">Examples</a></h3>
<p><strong>Invalid range:</strong></p>
<pre><code class="language-storybook">template BadRange {
age: 65..18 // Error: range min (65) must be &lt;= max (18)
}
</code></pre>
<p><strong>Strict template violation:</strong></p>
<pre><code class="language-storybook">template Rigid strict {
required_stat: 10
}
character Constrained from Rigid {
required_stat: 15
extra_field: 42 // Error: field 'extra_field' not allowed by strict template 'Rigid'
}
</code></pre>
<h2 id="behavior-tree-validation"><a class="header" href="#behavior-tree-validation">Behavior Tree Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>At least one node</td><td>Behavior body must contain at least one node</td><td>Error</td></tr>
<tr><td>Composite children</td><td><code>choose</code> and <code>then</code> require at least one child</td><td>Error</td></tr>
<tr><td>Decorator child</td><td>Decorators require exactly one child</td><td>Error</td></tr>
<tr><td>Subtree exists</td><td><code>include</code> must reference a defined behavior</td><td>Error</td></tr>
<tr><td>Expression validity</td><td>Condition expressions must be well-formed</td><td>Error</td></tr>
<tr><td>Duration format</td><td>Decorator durations must be valid (e.g., <code>5s</code>, <code>10m</code>)</td><td>Error</td></tr>
<tr><td>Repeat count valid</td><td><code>repeat N</code> requires N &gt;= 0</td><td>Error</td></tr>
<tr><td>Repeat range valid</td><td><code>repeat min..max</code> requires 0 &lt;= min &lt;= max</td><td>Error</td></tr>
<tr><td>Retry count valid</td><td><code>retry N</code> requires N &gt;= 1</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples-2"><a class="header" href="#examples-2">Examples</a></h3>
<p><strong>Empty composite:</strong></p>
<pre><code class="language-storybook">behavior Empty {
choose options {
// Error: 'choose' requires at least one child
}
}
</code></pre>
<p><strong>Invalid subtree reference:</strong></p>
<pre><code class="language-storybook">behavior Main {
include NonExistentBehavior // Error: behavior 'NonExistentBehavior' not defined
}
</code></pre>
<h2 id="life-arc-validation"><a class="header" href="#life-arc-validation">Life Arc Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>At least one state</td><td>Life arc must contain at least one state</td><td>Error</td></tr>
<tr><td>Unique state names</td><td>State names must be unique within the life arc</td><td>Error</td></tr>
<tr><td>Valid transitions</td><td>Transition targets must reference defined states</td><td>Error</td></tr>
<tr><td>Expression validity</td><td>Transition conditions must be well-formed</td><td>Error</td></tr>
<tr><td>Field targets valid</td><td>On-enter field references must resolve</td><td>Error</td></tr>
<tr><td>Reachable states</td><td>All states should be reachable from initial state</td><td>Warning</td></tr>
</tbody></table>
</div>
<h3 id="examples-3"><a class="header" href="#examples-3">Examples</a></h3>
<p><strong>Invalid transition target:</strong></p>
<pre><code class="language-storybook">life_arc Broken {
state active {
on timer_expired -&gt; nonexistent // Error: state 'nonexistent' not defined
}
}
</code></pre>
<p><strong>Unreachable state (warning):</strong></p>
<pre><code class="language-storybook">life_arc HasOrphan {
state start {
on ready -&gt; middle
}
state middle {
on done -&gt; end
}
state orphan {} // Warning: state 'orphan' is not reachable
state end {}
}
</code></pre>
<h2 id="schedule-validation"><a class="header" href="#schedule-validation">Schedule Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Time format</td><td>Times must be valid HH:MM or HH:MM:SS</td><td>Error</td></tr>
<tr><td>Extends exists</td><td>Base schedule must be defined</td><td>Error</td></tr>
<tr><td>No circular extends</td><td>Schedule chains cannot form cycles</td><td>Error</td></tr>
<tr><td>Named blocks unique</td><td>Block names must be unique within a schedule</td><td>Error</td></tr>
<tr><td>Action references valid</td><td>Action references must resolve to defined behaviors</td><td>Error</td></tr>
<tr><td>Constraint values valid</td><td>Temporal constraint values must reference defined enums</td><td>Error</td></tr>
<tr><td>Recurrence names unique</td><td>Recurrence names must be unique within a schedule</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples-4"><a class="header" href="#examples-4">Examples</a></h3>
<p><strong>Invalid time format:</strong></p>
<pre><code class="language-storybook">schedule Bad {
block work {
25:00 - 17:00 // Error: invalid hour '25'
action: work
}
}
</code></pre>
<p><strong>Circular extends:</strong></p>
<pre><code class="language-storybook">schedule A extends B { }
schedule B extends A { } // Error: circular schedule extension detected
</code></pre>
<h2 id="relationship-validation"><a class="header" href="#relationship-validation">Relationship Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>At least two participants</td><td>Relationships require &gt;= 2 participants</td><td>Error</td></tr>
<tr><td>Participants exist</td><td>All participant names must reference defined entities</td><td>Error</td></tr>
<tr><td>Unique participants</td><td>Each participant appears at most once</td><td>Error</td></tr>
<tr><td>Field type consistency</td><td>Fields must have valid value types</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples-5"><a class="header" href="#examples-5">Examples</a></h3>
<p><strong>Too few participants:</strong></p>
<pre><code class="language-storybook">relationship Lonely {
Martha // Error: relationship requires at least 2 participants
bond: 0.5
}
</code></pre>
<h2 id="species-validation"><a class="header" href="#species-validation">Species Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Unique name</td><td>Species names must be unique within their module</td><td>Error</td></tr>
<tr><td>No circular includes</td><td>Include chains cannot form cycles</td><td>Error</td></tr>
<tr><td>Includes exist</td><td>All included species must be defined</td><td>Error</td></tr>
<tr><td>Field type consistency</td><td>Fields must have valid values</td><td>Error</td></tr>
<tr><td>Prose tag uniqueness</td><td>Each prose tag can appear at most once</td><td>Error</td></tr>
</tbody></table>
</div>
<h2 id="enum-validation"><a class="header" href="#enum-validation">Enum Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Unique enum name</td><td>Enum names must be unique within their module</td><td>Error</td></tr>
<tr><td>Unique variants</td><td>Variant names must be unique within the enum</td><td>Error</td></tr>
<tr><td>Non-empty</td><td>Enums must have at least one variant</td><td>Error</td></tr>
<tr><td>Valid identifiers</td><td>Variants must follow identifier rules</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples-6"><a class="header" href="#examples-6">Examples</a></h3>
<p><strong>Duplicate variant:</strong></p>
<pre><code class="language-storybook">enum Size {
tiny,
small,
small, // Error: duplicate variant 'small' in enum 'Size'
large
}
</code></pre>
<h2 id="institution-and-location-validation"><a class="header" href="#institution-and-location-validation">Institution and Location Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Unique name</td><td>Names must be unique within their module</td><td>Error</td></tr>
<tr><td>Resource links valid</td><td>Behavior/schedule references must resolve</td><td>Error</td></tr>
<tr><td>Field type consistency</td><td>Fields must have valid values</td><td>Error</td></tr>
</tbody></table>
</div>
<h2 id="expression-validation"><a class="header" href="#expression-validation">Expression Validation</a></h2>
<p>Expressions are validated wherever they appear (life arc transitions, behavior tree conditions, if decorators).</p>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Type consistency</td><td>Both sides of comparison must have compatible types</td><td>Error</td></tr>
<tr><td>Boolean context</td><td>Logical operators require boolean operands</td><td>Error</td></tr>
<tr><td>Field existence</td><td>Referenced fields must exist on the entity</td><td>Error</td></tr>
<tr><td>Collection validity</td><td>Quantifiers require collection-typed expressions</td><td>Error</td></tr>
<tr><td>Variable scope</td><td>Quantifier variables only valid within their predicate</td><td>Error</td></tr>
<tr><td>Enum validity</td><td>Enum comparisons must reference defined values</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples-7"><a class="header" href="#examples-7">Examples</a></h3>
<p><strong>Type mismatch:</strong></p>
<pre><code class="language-storybook">life_arc TypeError {
state checking {
on count == "five" -&gt; done // Error: cannot compare int with string
}
state done {}
}
</code></pre>
<h2 id="use-statement-validation"><a class="header" href="#use-statement-validation">Use Statement Validation</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>Path exists</td><td>Imported paths must reference defined modules/items</td><td>Error</td></tr>
<tr><td>No circular imports</td><td>Modules cannot form circular dependency chains</td><td>Error</td></tr>
<tr><td>Valid identifiers</td><td>All path segments must be valid identifiers</td><td>Error</td></tr>
<tr><td>Grouped import validity</td><td>All items in <code>{}</code> must exist in the target module</td><td>Error</td></tr>
</tbody></table>
</div>
<h3 id="examples-8"><a class="header" href="#examples-8">Examples</a></h3>
<p><strong>Missing import:</strong></p>
<pre><code class="language-storybook">use schema::nonexistent::Thing; // Error: module 'schema::nonexistent' not found
</code></pre>
<h2 id="cross-file-validation"><a class="header" href="#cross-file-validation">Cross-File Validation</a></h2>
<p>When resolving across multiple <code>.sb</code> files, the compiler performs additional checks:</p>
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
<tr><td>All references resolve</td><td>Cross-file references must find their targets</td><td>Error</td></tr>
<tr><td>No naming conflicts</td><td>Declarations must not collide across files in the same module</td><td>Error</td></tr>
<tr><td>Import visibility</td><td>Only public declarations can be imported</td><td>Error</td></tr>
</tbody></table>
</div>
<h2 id="common-error-patterns"><a class="header" href="#common-error-patterns">Common Error Patterns</a></h2>
<h3 id="missing-definitions"><a class="header" href="#missing-definitions">Missing Definitions</a></h3>
<p>The most common error is referencing something that does not exist:</p>
<pre><code class="language-storybook">character Martha: Human from Baker {
specialty: sourdough
}
</code></pre>
<p>If <code>Human</code>, <code>Baker</code>, or the <code>sourdough</code> enum variant are not defined or imported, the compiler will report an error. Fix by adding the appropriate <code>use</code> statements:</p>
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Specialty};
use schema::templates::Baker;
use schema::beings::Human;
character Martha: Human from Baker {
specialty: sourdough
}
</code></pre>
<h3 id="circular-dependencies"><a class="header" href="#circular-dependencies">Circular Dependencies</a></h3>
<p>Circular references are rejected at every level:</p>
<ul>
<li>Templates including each other</li>
<li>Species including each other</li>
<li>Schedules extending each other</li>
<li>Modules importing each other</li>
</ul>
<p>Break cycles by restructuring into a hierarchy or extracting shared parts into a common module.</p>
<h3 id="type-mismatches"><a class="header" href="#type-mismatches">Type Mismatches</a></h3>
<p>Storybook has no implicit type coercion. Ensure values match their expected types:</p>
<pre><code class="language-storybook">// Wrong:
character Bad {
age: "twenty" // Error: expected int, got string
is_ready: 1 // Error: expected bool, got int
}
// Correct:
character Good {
age: 20
is_ready: true
}
</code></pre>
<h2 id="validation-summary"><a class="header" href="#validation-summary">Validation Summary</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Declaration</th><th>Key Constraints</th></tr></thead><tbody>
<tr><td>Character</td><td>Unique name, valid species/templates, no circular inheritance</td></tr>
<tr><td>Template</td><td>Unique name, valid includes, valid ranges, strict enforcement</td></tr>
<tr><td>Behavior</td><td>Non-empty, valid composites, valid decorators, valid subtrees</td></tr>
<tr><td>Life Arc</td><td>Non-empty, unique states, valid transitions, reachable states</td></tr>
<tr><td>Schedule</td><td>Valid times, valid extends chain, unique block names</td></tr>
<tr><td>Relationship</td><td>&gt;= 2 participants, valid references</td></tr>
<tr><td>Species</td><td>Unique name, valid includes, no cycles</td></tr>
<tr><td>Enum</td><td>Unique name, unique variants, non-empty</td></tr>
<tr><td>Institution</td><td>Unique name, valid resource links</td></tr>
<tr><td>Location</td><td>Unique name, valid field types</td></tr>
<tr><td>Use</td><td>Valid paths, no circular imports</td></tr>
</tbody></table>
</div>
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
<ul>
<li><a href="./10-characters.html">Characters</a> - Character-specific validation</li>
<li><a href="./11-behavior-trees.html">Behavior Trees</a> - Behavior validation</li>
<li><a href="./13-life-arcs.html">Life Arcs</a> - Life arc validation</li>
<li><a href="./14-schedules.html">Schedules</a> - Schedule validation</li>
<li><a href="./17-expressions.html">Expression Language</a> - Expression validation</li>
<li><a href="./18-value-types.html">Value Types</a> - Type system constraints</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../reference/18-value-types.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="../advanced/20-patterns.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../reference/18-value-types.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="../advanced/20-patterns.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>