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:
327
docs/book/tutorial/01-welcome.html
Normal file
327
docs/book/tutorial/01-welcome.html
Normal file
@@ -0,0 +1,327 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Welcome to Storybook - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="welcome-to-storybook"><a class="header" href="#welcome-to-storybook">Welcome to Storybook</a></h1>
|
||||
<blockquote>
|
||||
<p><strong>Bring characters to life with code that reads like stories.</strong></p>
|
||||
</blockquote>
|
||||
<p>Welcome! This tutorial will guide you through the Storybook language step by step. By the end, you will be able to create rich characters, define complex behaviors, build relationships, and model entire narrative worlds.</p>
|
||||
<h2 id="what-you-will-learn"><a class="header" href="#what-you-will-learn">What You Will Learn</a></h2>
|
||||
<p>In this tutorial, we follow Martha and her bakery family, using their daily lives to learn each concept:</p>
|
||||
<ol>
|
||||
<li><strong>Creating Characters</strong> - Define Martha with traits and descriptions</li>
|
||||
<li><strong>Your First Behavior Tree</strong> - Give characters decision-making abilities</li>
|
||||
<li><strong>Making Characters Act</strong> - Actions, conditions, and decorators</li>
|
||||
<li><strong>Advanced Behaviors</strong> - Subtrees, parameters, and complex patterns</li>
|
||||
<li><strong>Character Relationships</strong> - Model how characters interact</li>
|
||||
<li><strong>Schedules and Time</strong> - Give characters daily routines</li>
|
||||
<li><strong>Life Arcs</strong> - Track character development over time</li>
|
||||
</ol>
|
||||
<h2 id="what-is-storybook"><a class="header" href="#what-is-storybook">What is Storybook?</a></h2>
|
||||
<p>Storybook is a domain-specific language (DSL) for narrative simulation. It lets you describe:</p>
|
||||
<ul>
|
||||
<li><strong>Who</strong> characters are (traits, backstory, species)</li>
|
||||
<li><strong>What</strong> they do (behavior trees with decision logic)</li>
|
||||
<li><strong>How</strong> they relate to others (relationships with perspectives)</li>
|
||||
<li><strong>When</strong> they act (schedules and time-based routines)</li>
|
||||
<li><strong>How they change</strong> (life arcs and state machines)</li>
|
||||
</ul>
|
||||
<p>All of this in syntax designed to be readable and expressive.</p>
|
||||
<h2 id="your-first-storybook-file"><a class="header" href="#your-first-storybook-file">Your First Storybook File</a></h2>
|
||||
<p>Create a file called <code>hello.sb</code> and add this:</p>
|
||||
<pre><code class="language-storybook">character Martha {
|
||||
age: 34
|
||||
skill_level: 0.95
|
||||
|
||||
---description
|
||||
A master baker who runs the most popular bakery in town,
|
||||
known for her sourdough bread and apple pastries.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<p>That is it. You have defined a character with two numeric fields and a prose description block. Let us break it down:</p>
|
||||
<ul>
|
||||
<li><code>character Martha</code> declares a new character named Martha</li>
|
||||
<li><code>{ ... }</code> contains her attributes</li>
|
||||
<li><code>age: 34</code> is an integer field</li>
|
||||
<li><code>skill_level: 0.95</code> is a floating-point field (0.0 to 1.0)</li>
|
||||
<li><code>---description ... ---</code> is a prose block for narrative text</li>
|
||||
</ul>
|
||||
<h2 id="key-concepts"><a class="header" href="#key-concepts">Key Concepts</a></h2>
|
||||
<h3 id="everything-is-a-declaration"><a class="header" href="#everything-is-a-declaration">Everything is a Declaration</a></h3>
|
||||
<p>Storybook files contain <strong>declarations</strong> – named definitions of things in your world:</p>
|
||||
<pre><code class="language-storybook">character Martha { ... } // A person or creature
|
||||
behavior BakeRoutine { ... } // Decision-making logic
|
||||
relationship Family { ... } // A connection between entities
|
||||
schedule DailyRoutine { ... } // Time-based activities
|
||||
life_arc Career { ... } // How someone changes over time
|
||||
</code></pre>
|
||||
<h3 id="fields-hold-data"><a class="header" href="#fields-hold-data">Fields Hold Data</a></h3>
|
||||
<p>Fields use a simple <code>name: value</code> format:</p>
|
||||
<pre><code class="language-storybook">age: 34 // Integer
|
||||
skill_level: 0.95 // Float
|
||||
name: "Martha Baker" // String
|
||||
is_open: true // Boolean
|
||||
wake_time: 04:30 // Time
|
||||
bake_duration: 45m // Duration
|
||||
</code></pre>
|
||||
<h3 id="prose-blocks-tell-stories"><a class="header" href="#prose-blocks-tell-stories">Prose Blocks Tell Stories</a></h3>
|
||||
<p>Prose blocks embed narrative text directly in your definitions:</p>
|
||||
<pre><code class="language-storybook">---backstory
|
||||
Martha learned to bake from her grandmother, starting at age
|
||||
twelve with simple bread recipes. Over the years she mastered
|
||||
sourdough, pastries, and specialty cakes, eventually opening
|
||||
her own bakery.
|
||||
---
|
||||
</code></pre>
|
||||
<p>You can have multiple prose blocks with different tags (<code>backstory</code>, <code>appearance</code>, <code>personality</code>, etc.) in a single declaration.</p>
|
||||
<h2 id="project-structure"><a class="header" href="#project-structure">Project Structure</a></h2>
|
||||
<p>A typical Storybook project organizes files into directories:</p>
|
||||
<pre><code>my-world/
|
||||
schema/
|
||||
core_enums.sb // Enum definitions (skill levels, moods, etc.)
|
||||
templates.sb // Reusable trait sets
|
||||
beings.sb // Species definitions
|
||||
world/
|
||||
characters/
|
||||
martha.sb // Character definitions
|
||||
jane.sb
|
||||
behaviors/
|
||||
baking.sb // Behavior trees
|
||||
relationships/
|
||||
family.sb // Relationship definitions
|
||||
</code></pre>
|
||||
<p>Files reference each other using <code>use</code> statements:</p>
|
||||
<pre><code class="language-storybook">use schema::core_enums::SkillLevel;
|
||||
use schema::beings::Human;
|
||||
|
||||
character Martha: Human {
|
||||
skill_level: expert
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<p>Ready to create your first character? Head to <a href="./02-creating-characters.html">Creating Characters</a> to start building Martha in detail.</p>
|
||||
<hr />
|
||||
<p><strong>Tip</strong>: You do not need to memorize everything now. This tutorial builds concepts gradually, and you can always refer back to the <a href="../reference/09-overview.html">Reference Guide</a> for precise syntax details.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../index.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/02-creating-characters.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../index.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/02-creating-characters.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
477
docs/book/tutorial/02-creating-characters.html
Normal file
477
docs/book/tutorial/02-creating-characters.html
Normal file
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Creating Characters - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="creating-characters"><a class="header" href="#creating-characters">Creating Characters</a></h1>
|
||||
<p>Characters are the heart of every Storybook world. In this chapter, you will learn how to define characters with fields, prose blocks, species, and templates.</p>
|
||||
<h2 id="a-simple-character"><a class="header" href="#a-simple-character">A Simple Character</a></h2>
|
||||
<p>The simplest character has a name and some fields:</p>
|
||||
<pre><code class="language-storybook">character Martha {
|
||||
age: 34
|
||||
skill_level: 0.95
|
||||
is_open: true
|
||||
}
|
||||
</code></pre>
|
||||
<p>Fields use the <code>name: value</code> format. Storybook supports several value types:</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Example</th><th>Description</th></tr></thead><tbody>
|
||||
<tr><td>Integer</td><td><code>42</code></td><td>Whole numbers</td></tr>
|
||||
<tr><td>Float</td><td><code>0.85</code></td><td>Decimal numbers</td></tr>
|
||||
<tr><td>String</td><td><code>"hello"</code></td><td>Text in double quotes</td></tr>
|
||||
<tr><td>Boolean</td><td><code>true</code> / <code>false</code></td><td>Yes or no values</td></tr>
|
||||
<tr><td>Time</td><td><code>14:30</code></td><td>Clock times</td></tr>
|
||||
<tr><td>Duration</td><td><code>2h30m</code></td><td>Time intervals</td></tr>
|
||||
<tr><td>List</td><td><code>[1, 2, 3]</code></td><td>Ordered collections</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<h2 id="adding-descriptions-with-prose-blocks"><a class="header" href="#adding-descriptions-with-prose-blocks">Adding Descriptions with Prose Blocks</a></h2>
|
||||
<p>Prose blocks embed narrative text directly alongside data. They start and end with <code>---</code> and have a tag name:</p>
|
||||
<pre><code class="language-storybook">character Martha {
|
||||
age: 34
|
||||
skill_level: 0.95
|
||||
|
||||
---backstory
|
||||
Martha learned to bake from her grandmother, starting at age
|
||||
twelve with simple bread recipes. She mastered sourdough and
|
||||
pastries, eventually opening the most popular bakery in town.
|
||||
---
|
||||
|
||||
---appearance
|
||||
A confident woman in her mid-thirties, usually dusted with
|
||||
flour. Her hands are calloused from years of kneading dough.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<p>You can use any tag name you like. Common ones include <code>backstory</code>, <code>appearance</code>, <code>personality</code>, <code>motivation</code>, and <code>secrets</code>.</p>
|
||||
<h2 id="defining-species"><a class="header" href="#defining-species">Defining Species</a></h2>
|
||||
<p>Species define what a character fundamentally <em>is</em>. Define them separately, then reference them with the <code>:</code> syntax:</p>
|
||||
<pre><code class="language-storybook">species Human {
|
||||
lifespan: 70
|
||||
|
||||
---description
|
||||
Bipedal mammals with complex language and tool use.
|
||||
---
|
||||
}
|
||||
|
||||
species Cat {
|
||||
lifespan: 15
|
||||
|
||||
---description
|
||||
Domestic cats make loyal companions and effective
|
||||
pest control for bakeries.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<p>Now use species when creating characters:</p>
|
||||
<pre><code class="language-storybook">character Martha: Human {
|
||||
age: 34
|
||||
}
|
||||
|
||||
character Whiskers: Cat {
|
||||
friendly: true
|
||||
catches_mice: true
|
||||
}
|
||||
</code></pre>
|
||||
<p>The <code>: Human</code> part says “Martha is a Human.” She inherits the species’ fields (like <code>lifespan: 70</code>) automatically.</p>
|
||||
<p>A character can have only one species – you cannot be both Human and Cat.</p>
|
||||
<h3 id="but-what-about-hybrids"><a class="header" href="#but-what-about-hybrids">But what about hybrids?</a></h3>
|
||||
<p>If you want a character that combines traits from different sources, use <strong>composition with templates</strong> instead:</p>
|
||||
<pre><code class="language-storybook">species Human {
|
||||
lifespan: 70
|
||||
reasoning_ability: 1.0
|
||||
}
|
||||
|
||||
template CulinaryExpert {
|
||||
palate_sensitivity: 0.5..1.0
|
||||
recipes_mastered: 0..500
|
||||
can_identify_ingredients: true
|
||||
}
|
||||
|
||||
template BusinessOwner {
|
||||
business_acumen: 0.0..1.0
|
||||
manages_finances: true
|
||||
leadership: 0.0..1.0
|
||||
}
|
||||
|
||||
// A character combining species with multiple templates
|
||||
character Martha: Human from CulinaryExpert, BusinessOwner {
|
||||
age: 34
|
||||
|
||||
// From CulinaryExpert
|
||||
palate_sensitivity: 0.9
|
||||
recipes_mastered: 150
|
||||
can_identify_ingredients: true
|
||||
|
||||
// From BusinessOwner
|
||||
business_acumen: 0.8
|
||||
manages_finances: true
|
||||
leadership: 0.85
|
||||
|
||||
// Unique traits
|
||||
specialty: "sourdough"
|
||||
years_experience: 22
|
||||
|
||||
---personality
|
||||
A perfectionist in the kitchen who demands the best from her
|
||||
ingredients and her team. Warm with customers but exacting
|
||||
with her apprentices.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<p>By combining a species with templates, you can achieve any combination you need. The species defines <em>what</em> the character fundamentally is, while templates add <em>traits</em> from other sources.</p>
|
||||
<h2 id="reusing-traits-with-templates"><a class="header" href="#reusing-traits-with-templates">Reusing Traits with Templates</a></h2>
|
||||
<p>Templates define reusable sets of attributes. Characters inherit from them using the <code>from</code> keyword:</p>
|
||||
<pre><code class="language-storybook">template SkilledWorker {
|
||||
skill_level: 0.0..1.0
|
||||
years_experience: 0..50
|
||||
}
|
||||
|
||||
template Baker {
|
||||
include SkilledWorker
|
||||
specialty: "general"
|
||||
recipes_mastered: 0..500
|
||||
}
|
||||
</code></pre>
|
||||
<p>Notice the <code>0.0..1.0</code> syntax – that is a <strong>range</strong>. When a character uses this template, a specific value within that range is selected. Ranges are only valid in templates.</p>
|
||||
<p>Characters can inherit from multiple templates:</p>
|
||||
<pre><code class="language-storybook">character Martha: Human from Baker, BusinessOwner {
|
||||
age: 34
|
||||
skill_level: 0.95
|
||||
specialty: "sourdough"
|
||||
recipes_mastered: 150
|
||||
}
|
||||
</code></pre>
|
||||
<p>The <code>from Baker, BusinessOwner</code> part says “Martha has the traits from both templates.” You can override any inherited field by specifying it directly.</p>
|
||||
<h2 id="species-vs-templates"><a class="header" href="#species-vs-templates">Species vs. Templates</a></h2>
|
||||
<p>Understanding the difference is important:</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th></th><th>Species (<code>:</code>)</th><th>Templates (<code>from</code>)</th></tr></thead><tbody>
|
||||
<tr><td><strong>Meaning</strong></td><td>What the character <em>is</em></td><td>What the character <em>has</em></td></tr>
|
||||
<tr><td><strong>Count</strong></td><td>Exactly one</td><td>Zero or more</td></tr>
|
||||
<tr><td><strong>Example</strong></td><td><code>: Human</code></td><td><code>from Baker, BusinessOwner</code></td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<p>Think of it this way: a character <strong>is</strong> a Human, but <strong>has</strong> baking skills and business acumen.</p>
|
||||
<h2 id="field-resolution"><a class="header" href="#field-resolution">Field Resolution</a></h2>
|
||||
<p>When a character inherits from multiple sources, fields are resolved in priority order:</p>
|
||||
<ol>
|
||||
<li><strong>Species fields</strong> (lowest priority)</li>
|
||||
<li><strong>Template fields</strong> (left to right in the <code>from</code> clause)</li>
|
||||
<li><strong>Character fields</strong> (highest priority – always wins)</li>
|
||||
</ol>
|
||||
<pre><code class="language-storybook">species Human {
|
||||
speed: 1.0
|
||||
}
|
||||
|
||||
template Warrior {
|
||||
speed: 1.5
|
||||
strength: 10
|
||||
}
|
||||
|
||||
template Berserker {
|
||||
speed: 2.0
|
||||
strength: 15
|
||||
}
|
||||
|
||||
character Conan: Human from Warrior, Berserker {
|
||||
strength: 20
|
||||
}
|
||||
// Resolved: speed = 2.0 (Berserker), strength = 20 (Conan)
|
||||
</code></pre>
|
||||
<h2 id="using-enums-for-controlled-values"><a class="header" href="#using-enums-for-controlled-values">Using Enums for Controlled Values</a></h2>
|
||||
<p>Enums define a fixed set of named values. They prevent typos and ensure consistency:</p>
|
||||
<pre><code class="language-storybook">enum SkillLevel {
|
||||
Novice,
|
||||
Beginner,
|
||||
Intermediate,
|
||||
Advanced,
|
||||
Expert,
|
||||
Master
|
||||
}
|
||||
|
||||
enum Specialty {
|
||||
Sourdough,
|
||||
Pastries,
|
||||
Cakes,
|
||||
Bread,
|
||||
Confections
|
||||
}
|
||||
</code></pre>
|
||||
<p>Use enum values as field values:</p>
|
||||
<pre><code class="language-storybook">character Martha: Human {
|
||||
skill_level: Master
|
||||
specialty: Sourdough
|
||||
}
|
||||
</code></pre>
|
||||
<p>If you write <code>skill_level: Professional</code>, the compiler will catch the mistake because <code>Professional</code> is not defined in the <code>SkillLevel</code> enum.</p>
|
||||
<h2 id="importing-across-files"><a class="header" href="#importing-across-files">Importing Across Files</a></h2>
|
||||
<p>Real projects split definitions across multiple files. Use the <code>use</code> statement to import:</p>
|
||||
<pre><code class="language-storybook">// In world/characters/martha.sb
|
||||
use schema::core_enums::{SkillLevel, Specialty};
|
||||
use schema::templates::Baker;
|
||||
use schema::beings::Human;
|
||||
|
||||
character Martha: Human from Baker {
|
||||
skill_level: Master
|
||||
specialty: Sourdough
|
||||
}
|
||||
</code></pre>
|
||||
<p>The <code>use schema::core_enums::{SkillLevel, Specialty}</code> line imports two enums from the <code>schema/core_enums.sb</code> file. You can also import everything with <code>use schema::core_enums::*</code>.</p>
|
||||
<h2 id="putting-it-all-together"><a class="header" href="#putting-it-all-together">Putting It All Together</a></h2>
|
||||
<p>Here is a complete character definition with all the features:</p>
|
||||
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Specialty};
|
||||
use schema::templates::{Baker, BusinessOwner};
|
||||
use schema::beings::Human;
|
||||
|
||||
character Martha: Human from Baker, BusinessOwner {
|
||||
// Core identity
|
||||
age: 34
|
||||
skill_level: Master
|
||||
specialty: Sourdough
|
||||
|
||||
// Professional
|
||||
years_experience: 22
|
||||
recipes_mastered: 150
|
||||
can_teach: true
|
||||
|
||||
// Business
|
||||
business_acumen: 0.8
|
||||
leadership: 0.85
|
||||
|
||||
---backstory
|
||||
Martha learned to bake from her grandmother, starting at age
|
||||
twelve with simple bread recipes. She mastered sourdough and
|
||||
pastries, eventually opening the most popular bakery in town.
|
||||
---
|
||||
|
||||
---personality
|
||||
A perfectionist in the kitchen who demands the best from her
|
||||
ingredients and her team. Warm with customers but exacting
|
||||
with her apprentices. Known for arriving at 4 AM to start
|
||||
the morning batch.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<p>Now that Martha exists, let us give her something to do. In <a href="./03-first-behavior-tree.html">Your First Behavior Tree</a>, you will learn how characters make decisions.</p>
|
||||
<hr />
|
||||
<p><strong>Reference</strong>: For complete character syntax details, see the <a href="../reference/10-characters.html">Characters Reference</a>.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/01-welcome.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/03-first-behavior-tree.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/01-welcome.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/03-first-behavior-tree.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
381
docs/book/tutorial/03-first-behavior-tree.html
Normal file
381
docs/book/tutorial/03-first-behavior-tree.html
Normal file
@@ -0,0 +1,381 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Your First Behavior Tree - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="your-first-behavior-tree"><a class="header" href="#your-first-behavior-tree">Your First Behavior Tree</a></h1>
|
||||
<p>Behavior trees define how characters make decisions. They model the thought process: “Try this first, and if it fails, try that instead.” In this chapter, you will create your first behavior tree for Martha.</p>
|
||||
<h2 id="what-is-a-behavior-tree"><a class="header" href="#what-is-a-behavior-tree">What is a Behavior Tree?</a></h2>
|
||||
<p>A behavior tree is a hierarchy of nodes that executes from top to bottom. Each node either <strong>succeeds</strong> or <strong>fails</strong>, and the tree uses that result to decide what to do next.</p>
|
||||
<p>There are two fundamental building blocks:</p>
|
||||
<ul>
|
||||
<li><strong><code>choose</code></strong> (Selector): Try children in order until one succeeds. Think “try A, else try B, else try C.”</li>
|
||||
<li><strong><code>then</code></strong> (Sequence): Run children in order, stopping if any fails. Think “do A, then B, then C – all must succeed.”</li>
|
||||
</ul>
|
||||
<h2 id="your-first-tree"><a class="header" href="#your-first-tree">Your First Tree</a></h2>
|
||||
<p>Let us give Martha a simple baking behavior:</p>
|
||||
<pre><code class="language-storybook">behavior Martha_BakeRoutine {
|
||||
choose what_to_do {
|
||||
then fill_special_orders {
|
||||
CheckSpecialOrders
|
||||
PrepareSpecialIngredients
|
||||
BakeSpecialItem
|
||||
}
|
||||
|
||||
then daily_bread {
|
||||
MixDough
|
||||
KneadDough
|
||||
BakeLoaves
|
||||
}
|
||||
|
||||
CleanWorkstation
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Reading this as a story:</p>
|
||||
<blockquote>
|
||||
<p>Martha will <strong>choose</strong> what to do. First, she tries to <strong>fill special orders</strong>: she checks for orders, prepares special ingredients, and bakes the item. If that path fails (maybe there are no special orders), she tries <strong>daily bread</strong>: she mixes dough, kneads it, and bakes loaves. If even that fails, she simply cleans her workstation.</p>
|
||||
</blockquote>
|
||||
<h2 id="understanding-choose-selector"><a class="header" href="#understanding-choose-selector">Understanding choose (Selector)</a></h2>
|
||||
<p>A <code>choose</code> node tries its children one at a time. As soon as one succeeds, it stops and returns success. If all children fail, it returns failure.</p>
|
||||
<pre><code class="language-storybook">choose response {
|
||||
HandleUrgentOrder // Try first: handle urgent order
|
||||
ServeCustomer // If that fails: serve a customer
|
||||
RestockShelves // If that fails: restock
|
||||
}
|
||||
</code></pre>
|
||||
<p>This is like a priority list – the first successful option wins.</p>
|
||||
<h2 id="understanding-then-sequence"><a class="header" href="#understanding-then-sequence">Understanding then (Sequence)</a></h2>
|
||||
<p>A <code>then</code> node runs its children in order. If any child fails, the whole sequence fails and stops. All children must succeed for the sequence to succeed.</p>
|
||||
<pre><code class="language-storybook">then make_sourdough {
|
||||
MixDough // Must succeed
|
||||
KneadDough // Must succeed
|
||||
FirstRise // Must succeed
|
||||
ShapeLoaves // Must succeed
|
||||
}
|
||||
</code></pre>
|
||||
<p>If <code>MixDough</code> fails (no flour available), the whole process stops.</p>
|
||||
<h2 id="naming-your-nodes"><a class="header" href="#naming-your-nodes">Naming Your Nodes</a></h2>
|
||||
<p>Both <code>choose</code> and <code>then</code> accept optional labels:</p>
|
||||
<pre><code class="language-storybook">choose daily_priority {
|
||||
then morning_baking { ... }
|
||||
then afternoon_sales { ... }
|
||||
}
|
||||
</code></pre>
|
||||
<p>Labels are optional but highly recommended. They make your trees readable as narratives and help with debugging. Compare:</p>
|
||||
<pre><code class="language-storybook">// Without labels (hard to read)
|
||||
choose {
|
||||
then { MixDough, BakeLoaves }
|
||||
then { ServeCustomer, CollectPayment }
|
||||
}
|
||||
|
||||
// With labels (reads like a story)
|
||||
choose priority {
|
||||
then baking { MixDough, BakeLoaves }
|
||||
then sales { ServeCustomer, CollectPayment }
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="combining-choose-and-then"><a class="header" href="#combining-choose-and-then">Combining choose and then</a></h2>
|
||||
<p>Behavior trees become powerful when you nest selectors and sequences:</p>
|
||||
<pre><code class="language-storybook">behavior Jane_PastryRoutine {
|
||||
choose pastry_priorities {
|
||||
// Highest priority: fill custom cake orders
|
||||
then custom_orders {
|
||||
ReviewCakeOrder
|
||||
DesignDecoration
|
||||
BakeAndDecorate
|
||||
PackageForPickup
|
||||
}
|
||||
|
||||
// If no orders: prepare display pastries
|
||||
then display_pastries {
|
||||
RollPastryDough
|
||||
PrepareFillings
|
||||
AssemblePastries
|
||||
ArrangeDisplay
|
||||
}
|
||||
|
||||
// Default: experiment with new recipes
|
||||
ExperimentWithFlavors
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Reading this as narrative:</p>
|
||||
<blockquote>
|
||||
<p>Jane always prioritizes custom cake orders. She reviews the order, designs the decoration, bakes and decorates, then packages it. If there are no orders, she prepares display pastries. If there is nothing else to do, she experiments with new flavors.</p>
|
||||
</blockquote>
|
||||
<h2 id="actions"><a class="header" href="#actions">Actions</a></h2>
|
||||
<p>The leaf nodes in a behavior tree are <strong>actions</strong> – concrete things a character does:</p>
|
||||
<pre><code class="language-storybook">MixDough // Simple action
|
||||
KneadDough // Simple action
|
||||
ServeCustomer // Simple action
|
||||
</code></pre>
|
||||
<p>Actions are identifiers that the runtime interprets. They represent the actual behaviors executed in your simulation.</p>
|
||||
<h2 id="a-complete-example"><a class="header" href="#a-complete-example">A Complete Example</a></h2>
|
||||
<p>Here is a behavior tree for the morning rush at the bakery:</p>
|
||||
<pre><code class="language-storybook">behavior Bakery_MorningRush {
|
||||
---description
|
||||
Handles the busy morning rush when customers are
|
||||
lining up for fresh bread and pastries.
|
||||
---
|
||||
|
||||
choose morning_priority {
|
||||
then serve_waiting_customer {
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
ThankCustomer
|
||||
}
|
||||
|
||||
then restock_display {
|
||||
CheckDisplayLevels
|
||||
FetchFromKitchen
|
||||
ArrangeOnShelves
|
||||
}
|
||||
|
||||
then quick_bake {
|
||||
CheckInventory
|
||||
StartQuickBatch
|
||||
MonitorOven
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Notice the prose block (<code>---description ... ---</code>) at the top of the behavior. You can document what the behavior does right alongside the code.</p>
|
||||
<h2 id="behavior-character-connection"><a class="header" href="#behavior-character-connection">Behavior-Character Connection</a></h2>
|
||||
<p>Characters link to behaviors using the <code>uses behaviors</code> clause:</p>
|
||||
<pre><code class="language-storybook">character Martha: Human {
|
||||
age: 34
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: Martha_BakeRoutine },
|
||||
{ tree: HandleEmergency }
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
<p>This tells the simulation that Martha uses two behavior trees. We will cover advanced behavior linking (priorities, conditions) in <a href="./04-making-characters-act.html">Making Characters Act</a>.</p>
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<p>Your behavior trees so far make decisions between options and run sequences of actions. In <a href="./04-making-characters-act.html">Making Characters Act</a>, you will learn how to add conditions, decorators, and parameters to create truly dynamic behaviors.</p>
|
||||
<hr />
|
||||
<p><strong>Reference</strong>: For complete behavior tree syntax, see the <a href="../reference/11-behavior-trees.html">Behavior Trees Reference</a>.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/02-creating-characters.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/04-making-characters-act.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/02-creating-characters.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/04-making-characters-act.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
453
docs/book/tutorial/04-making-characters-act.html
Normal file
453
docs/book/tutorial/04-making-characters-act.html
Normal file
@@ -0,0 +1,453 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Making Characters Act - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="making-characters-act"><a class="header" href="#making-characters-act">Making Characters Act</a></h1>
|
||||
<p>In the previous chapter, you created behavior trees with selectors and sequences. Now you will add conditions, action parameters, and decorators to create dynamic, responsive behaviors.</p>
|
||||
<h2 id="conditions-if-and-when"><a class="header" href="#conditions-if-and-when">Conditions: if and when</a></h2>
|
||||
<p>Conditions let behavior trees react to the world. Use <code>if</code> or <code>when</code> to test a condition before proceeding:</p>
|
||||
<pre><code class="language-storybook">behavior Martha_React {
|
||||
choose response {
|
||||
then bake_path {
|
||||
if(inventory_sufficient)
|
||||
StartBaking
|
||||
}
|
||||
|
||||
then restock_path {
|
||||
if(inventory_low)
|
||||
OrderSupplies
|
||||
}
|
||||
|
||||
CleanWorkstation
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p><code>if(inventory_sufficient)</code> succeeds when inventory is sufficient, and fails otherwise. If it fails, the entire <code>bake_path</code> sequence fails, and the tree moves on to the next option.</p>
|
||||
<p><code>if</code> and <code>when</code> are interchangeable – use whichever reads more naturally:</p>
|
||||
<pre><code class="language-storybook">// "if" for state checks
|
||||
if(health < 20)
|
||||
|
||||
// "when" for event-like conditions
|
||||
when(alarm_triggered)
|
||||
</code></pre>
|
||||
<h3 id="condition-expressions"><a class="header" href="#condition-expressions">Condition Expressions</a></h3>
|
||||
<p>Conditions support comparisons and logical operators:</p>
|
||||
<pre><code class="language-storybook">// Comparisons
|
||||
if(health < 20)
|
||||
if(distance > 100)
|
||||
if(name == "Martha")
|
||||
if(status is Curious) // 'is' is syntactic sugar for ==
|
||||
|
||||
// Logical operators
|
||||
if(hungry and tired)
|
||||
if(rich or lucky)
|
||||
if(not is_dangerous)
|
||||
|
||||
// Combined
|
||||
if(health < 50 and not has_potion)
|
||||
if((age > 18 and age < 65) or is_veteran)
|
||||
</code></pre>
|
||||
<h2 id="action-parameters"><a class="header" href="#action-parameters">Action Parameters</a></h2>
|
||||
<p>Actions can take named parameters using parenthesis syntax:</p>
|
||||
<pre><code class="language-storybook">behavior Martha_BakeSpecial {
|
||||
then baking {
|
||||
MixDough(recipe: "sourdough", quantity: 10)
|
||||
KneadDough(duration: 15m)
|
||||
BakeLoaves(temperature: 230, duration: 35m)
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Parameters are fields inside <code>( )</code> after the action name. They let you customize behavior without defining separate actions for each variation.</p>
|
||||
<h2 id="decorators"><a class="header" href="#decorators">Decorators</a></h2>
|
||||
<p>Decorators wrap a single child node and modify its behavior. They are your tools for timing, repetition, and conditional execution.</p>
|
||||
<h3 id="repeat--looping"><a class="header" href="#repeat--looping">repeat – Looping</a></h3>
|
||||
<pre><code class="language-storybook">// Infinite repeat (checks oven forever)
|
||||
repeat {
|
||||
CheckOvenTemperature
|
||||
}
|
||||
|
||||
// Repeat exactly 3 times
|
||||
repeat(3) {
|
||||
KneadDough
|
||||
}
|
||||
|
||||
// Repeat between 2 and 5 times (random)
|
||||
repeat(2..5) {
|
||||
FoldDough
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="invert--flip-results"><a class="header" href="#invert--flip-results">invert – Flip Results</a></h3>
|
||||
<p>Inverts success/failure. Useful for “if NOT” conditions:</p>
|
||||
<pre><code class="language-storybook">behavior SafeBake {
|
||||
choose options {
|
||||
then bake_safely {
|
||||
invert { OvenOverheating } // Succeeds if oven is NOT overheating
|
||||
ContinueBaking
|
||||
}
|
||||
|
||||
StopAndInspect
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="retry--try-again-on-failure"><a class="header" href="#retry--try-again-on-failure">retry – Try Again on Failure</a></h3>
|
||||
<p>Retries the child up to N times if it fails:</p>
|
||||
<pre><code class="language-storybook">retry(3) {
|
||||
LightOven // Try up to 3 times before giving up
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="timeout--time-limits"><a class="header" href="#timeout--time-limits">timeout – Time Limits</a></h3>
|
||||
<p>Fails the child if it does not complete within the duration:</p>
|
||||
<pre><code class="language-storybook">timeout(10s) {
|
||||
WaitForDoughToRise // Must finish within 10 seconds
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="cooldown--rate-limiting"><a class="header" href="#cooldown--rate-limiting">cooldown – Rate Limiting</a></h3>
|
||||
<p>Prevents the child from running again within the cooldown period:</p>
|
||||
<pre><code class="language-storybook">cooldown(30s) {
|
||||
CheckOvenTemperature // Can only check once every 30 seconds
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="if-as-decorator-guard"><a class="header" href="#if-as-decorator-guard">if as Decorator (Guard)</a></h3>
|
||||
<p>The <code>if</code> decorator only runs the child when a condition is true:</p>
|
||||
<pre><code class="language-storybook">if(has_special_orders) {
|
||||
PrepareSpecialBatch // Only prepare when there are orders
|
||||
}
|
||||
</code></pre>
|
||||
<p>This is different from <code>if</code> as a condition node. As a decorator, <code>if</code> wraps a child and gates its execution. As a condition node, <code>if</code> is a simple pass/fail check inline in a sequence.</p>
|
||||
<h3 id="succeed_always-and-fail_always"><a class="header" href="#succeed_always-and-fail_always">succeed_always and fail_always</a></h3>
|
||||
<p>Force a result regardless of the child:</p>
|
||||
<pre><code class="language-storybook">// Try bonus task, but don't fail the routine if it fails
|
||||
succeed_always {
|
||||
ExperimentWithNewRecipe
|
||||
}
|
||||
|
||||
// Temporarily disable a feature
|
||||
fail_always {
|
||||
UntestedBakingMethod
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="combining-decorators"><a class="header" href="#combining-decorators">Combining Decorators</a></h2>
|
||||
<p>Decorators can nest for complex control:</p>
|
||||
<pre><code class="language-storybook">behavior ResilientAction {
|
||||
// Only run if oven is ready, with 20s timeout, retrying up to 3 times
|
||||
if(oven_ready) {
|
||||
timeout(20s) {
|
||||
retry(3) {
|
||||
BakeDelicateItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Execution flows outside-in: first the <code>if</code> checks the oven, then the timeout starts, then the retry begins.</p>
|
||||
<h2 id="subtree-references"><a class="header" href="#subtree-references">Subtree References</a></h2>
|
||||
<p>The <code>include</code> keyword references another behavior tree, enabling reuse:</p>
|
||||
<pre><code class="language-storybook">behavior SourdoughRecipe {
|
||||
then sourdough {
|
||||
MixDough(recipe: "sourdough", quantity: 10)
|
||||
KneadDough(duration: 15m)
|
||||
FirstRise(duration: 2h)
|
||||
ShapeLoaves
|
||||
}
|
||||
}
|
||||
|
||||
behavior Martha_DailyRoutine {
|
||||
choose daily_priority {
|
||||
then special_orders {
|
||||
if(has_special_orders)
|
||||
include SpecialOrderBehavior
|
||||
}
|
||||
|
||||
include SourdoughRecipe // Reuse sourdough behavior
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Subtrees help you avoid duplicating behavior logic. You can also reference behaviors from other modules using qualified paths:</p>
|
||||
<pre><code class="language-storybook">include behaviors::baking::sourdough
|
||||
include behaviors::service::greet_customer
|
||||
</code></pre>
|
||||
<h2 id="behavior-linking-with-priorities"><a class="header" href="#behavior-linking-with-priorities">Behavior Linking with Priorities</a></h2>
|
||||
<p>Characters can link to multiple behaviors with priorities and conditions:</p>
|
||||
<pre><code class="language-storybook">character Martha: Human {
|
||||
uses behaviors: [
|
||||
{
|
||||
tree: BakerRoutine
|
||||
priority: normal
|
||||
},
|
||||
{
|
||||
tree: HandleEmergency
|
||||
when: emergency_detected
|
||||
priority: high
|
||||
},
|
||||
{
|
||||
tree: HandleHealthInspection
|
||||
when: inspector_present
|
||||
priority: critical
|
||||
}
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
<p>The runtime evaluates behaviors by priority (critical > high > normal > low). Higher-priority behaviors preempt lower-priority ones when their conditions are met.</p>
|
||||
<h2 id="a-complete-example"><a class="header" href="#a-complete-example">A Complete Example</a></h2>
|
||||
<p>Here is a complete behavior tree for handling the morning rush:</p>
|
||||
<pre><code class="language-storybook">behavior MorningRush_Routine {
|
||||
---description
|
||||
The bakery's morning rush routine: serve customers, restock,
|
||||
and keep the ovens running.
|
||||
---
|
||||
|
||||
repeat {
|
||||
then rush_cycle {
|
||||
// Serve any waiting customers
|
||||
choose service_mode {
|
||||
then serve_regular {
|
||||
if(customer_waiting)
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
}
|
||||
|
||||
then restock {
|
||||
if(display_low)
|
||||
FetchFromKitchen
|
||||
ArrangeOnShelves
|
||||
}
|
||||
}
|
||||
|
||||
// Check ovens between customers
|
||||
timeout(5s) {
|
||||
CheckAllOvens
|
||||
}
|
||||
|
||||
PrepareNextBatch
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>This tree repeats forever: serve a customer or restock the display, check the ovens, and prepare the next batch.</p>
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<p>You now know the full toolkit for behavior trees. In <a href="./05-advanced-behaviors.html">Advanced Behaviors</a>, you will learn patterns for building complex AI systems with nested trees, state-based switching, and modular design.</p>
|
||||
<hr />
|
||||
<p><strong>Reference</strong>: See <a href="../reference/12-decorators.html">Decorators Reference</a> for all decorator types and <a href="../reference/17-expressions.html">Expression Language</a> for complete condition syntax.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/03-first-behavior-tree.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/05-advanced-behaviors.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/03-first-behavior-tree.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/05-advanced-behaviors.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
417
docs/book/tutorial/05-advanced-behaviors.html
Normal file
417
docs/book/tutorial/05-advanced-behaviors.html
Normal file
@@ -0,0 +1,417 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Advanced Behaviors - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="advanced-behaviors"><a class="header" href="#advanced-behaviors">Advanced Behaviors</a></h1>
|
||||
<p>You have learned the fundamentals of behavior trees. This chapter covers advanced patterns: complex decision hierarchies, modular design with subtrees, and state-driven behavior.</p>
|
||||
<h2 id="deep-decision-trees"><a class="header" href="#deep-decision-trees">Deep Decision Trees</a></h2>
|
||||
<p>Real characters need layered decision-making. Nest selectors and sequences to create rich AI:</p>
|
||||
<pre><code class="language-storybook">behavior Baker_DailyAI {
|
||||
choose daily_activity {
|
||||
// Morning: Prepare the bakery
|
||||
then morning_prep {
|
||||
if(time_is_morning)
|
||||
then prep_sequence {
|
||||
LightOven
|
||||
PrepareDough
|
||||
StartFirstBatch
|
||||
}
|
||||
}
|
||||
|
||||
// Day: Serve customers
|
||||
then day_service {
|
||||
if(time_is_daytime)
|
||||
choose service_mode {
|
||||
then serve_customer {
|
||||
if(customer_waiting)
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
}
|
||||
|
||||
then restock {
|
||||
if(display_low)
|
||||
FetchFromKitchen
|
||||
}
|
||||
|
||||
CleanCounter
|
||||
}
|
||||
}
|
||||
|
||||
// Evening: Close up
|
||||
then evening_close {
|
||||
if(time_is_evening)
|
||||
then close_sequence {
|
||||
TurnOffOvens
|
||||
CleanKitchen
|
||||
CountRegister
|
||||
LockUp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Each level of nesting refines the decision. The outer <code>choose</code> selects the time of day; inner nodes handle the specifics.</p>
|
||||
<h2 id="modular-subtrees"><a class="header" href="#modular-subtrees">Modular Subtrees</a></h2>
|
||||
<p>Large behavior trees become unwieldy. Break them into focused subtrees and compose with <code>include</code>:</p>
|
||||
<pre><code class="language-storybook">// Focused subtree: just baking
|
||||
behavior Baking_Sourdough {
|
||||
then sourdough_sequence {
|
||||
MixDough
|
||||
KneadDough
|
||||
FirstRise
|
||||
ShapeLoaves
|
||||
}
|
||||
}
|
||||
|
||||
// Focused subtree: just customer service
|
||||
behavior Service_ServeCustomer {
|
||||
then service_sequence {
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
}
|
||||
}
|
||||
|
||||
// Composition: combine subtrees
|
||||
behavior Martha_FullDay {
|
||||
choose activity {
|
||||
then morning_baking {
|
||||
if(time_is_morning)
|
||||
include Baking_Sourdough
|
||||
include Baking_Pastries
|
||||
}
|
||||
|
||||
then afternoon_sales {
|
||||
if(time_is_afternoon)
|
||||
include Service_ServeCustomer
|
||||
}
|
||||
|
||||
CleanWorkstation
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Benefits of modular subtrees:</p>
|
||||
<ul>
|
||||
<li>Each subtree is testable in isolation</li>
|
||||
<li>Multiple characters can share subtrees</li>
|
||||
<li>Changes propagate automatically</li>
|
||||
</ul>
|
||||
<h2 id="conditional-behavior-selection"><a class="header" href="#conditional-behavior-selection">Conditional Behavior Selection</a></h2>
|
||||
<p>Use conditions to switch between behavioral modes:</p>
|
||||
<pre><code class="language-storybook">behavior SmartBaker {
|
||||
choose strategy {
|
||||
// Busy mode when there are many customers
|
||||
then busy_mode {
|
||||
if(customer_count > 5 and inventory_sufficient)
|
||||
choose rush_tactics {
|
||||
ServeFastOrder
|
||||
QuickRestock
|
||||
ExpressBake
|
||||
}
|
||||
}
|
||||
|
||||
// Careful mode when supplies are low
|
||||
then careful_mode {
|
||||
if(inventory_low or special_ingredients_missing)
|
||||
choose conservation_tactics {
|
||||
ReducePortions
|
||||
SubstituteIngredients
|
||||
OrderEmergencySupply
|
||||
}
|
||||
}
|
||||
|
||||
// Normal mode otherwise
|
||||
then normal_mode {
|
||||
if(customer_count <= 5 and inventory_sufficient)
|
||||
StandardRoutine
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="decorator-combinations"><a class="header" href="#decorator-combinations">Decorator Combinations</a></h2>
|
||||
<p>Combine decorators to build sophisticated control patterns:</p>
|
||||
<pre><code class="language-storybook">behavior Baker_SpecialRecipe {
|
||||
// Only when inventory is sufficient
|
||||
if(has_special_ingredients) {
|
||||
// Limited to once per hour
|
||||
cooldown(1h) {
|
||||
// Must complete within 30 minutes
|
||||
timeout(30m) {
|
||||
// Try up to 3 times
|
||||
retry(3) {
|
||||
then bake_special {
|
||||
PrepareSpecialDough
|
||||
BakeAtPreciseTemperature
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="prose-documentation"><a class="header" href="#prose-documentation">Prose Documentation</a></h2>
|
||||
<p>Add narrative context to complex behaviors with prose blocks:</p>
|
||||
<pre><code class="language-storybook">behavior Elena_TrainingSession {
|
||||
---description
|
||||
Elena practicing a new recipe under Martha's guidance.
|
||||
Uses retry decorator for persistence and if for
|
||||
checking readiness.
|
||||
---
|
||||
|
||||
choose training_strategy {
|
||||
then practice_supervised {
|
||||
if(martha_available)
|
||||
retry(3) {
|
||||
then attempt_recipe {
|
||||
ReviewRecipeSteps
|
||||
MeasureIngredients
|
||||
MixAndKnead
|
||||
CheckWithMartha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
then practice_solo {
|
||||
if(not martha_available)
|
||||
then solo_attempt {
|
||||
ReviewRecipeNotes
|
||||
AttemptRecipeFromMemory
|
||||
TasteTestResult
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="design-tips"><a class="header" href="#design-tips">Design Tips</a></h2>
|
||||
<p><strong>Prefer shallow trees</strong>: Break deep nesting into subtrees with <code>include</code>.</p>
|
||||
<p><strong>Name every composite node</strong>: Labels make trees self-documenting.</p>
|
||||
<p><strong>Use decorators for control flow</strong>: Timing, repetition, and gating belong in decorators, not in action logic.</p>
|
||||
<p><strong>Keep actions atomic</strong>: Each action should do one thing. Complex operations are sequences of simple actions.</p>
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<p>Characters do not exist in isolation. In <a href="./06-relationships.html">Character Relationships</a>, you will model how characters connect to each other – friendships, rivalries, parent-child bonds, and more.</p>
|
||||
<hr />
|
||||
<p><strong>Reference</strong>: See <a href="../reference/11-behavior-trees.html">Behavior Trees Reference</a> and <a href="../reference/12-decorators.html">Decorators Reference</a> for complete syntax.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/04-making-characters-act.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/06-relationships.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/04-making-characters-act.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/06-relationships.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
399
docs/book/tutorial/06-relationships.html
Normal file
399
docs/book/tutorial/06-relationships.html
Normal file
@@ -0,0 +1,399 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Character Relationships - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="character-relationships"><a class="header" href="#character-relationships">Character Relationships</a></h1>
|
||||
<p>Characters exist in a web of connections – friendships, rivalries, parent-child bonds, and complex power dynamics. In Storybook, relationships are first-class declarations that capture these connections with nuance and perspective.</p>
|
||||
<h2 id="basic-relationships"><a class="header" href="#basic-relationships">Basic Relationships</a></h2>
|
||||
<p>The simplest relationship connects two characters with shared fields:</p>
|
||||
<pre><code class="language-storybook">relationship MarthaAndEmma {
|
||||
Martha as parent {}
|
||||
Emma as child {}
|
||||
|
||||
bond: 0.95
|
||||
type: "parent_child"
|
||||
}
|
||||
</code></pre>
|
||||
<p>This says Martha and Emma share a relationship with a bond strength of 0.95 (very close). The <code>bond</code> field is shared – it applies equally to both participants.</p>
|
||||
<h2 id="adding-roles"><a class="header" href="#adding-roles">Adding Roles</a></h2>
|
||||
<p>Roles label each participant’s function in the relationship:</p>
|
||||
<pre><code class="language-storybook">relationship ParentChild {
|
||||
Martha as parent
|
||||
Emma as child
|
||||
|
||||
bond: 0.95
|
||||
guardianship: true
|
||||
}
|
||||
</code></pre>
|
||||
<p>The <code>as parent</code> and <code>as child</code> labels clarify who plays which role. Roles are descriptive – you can use any name that makes sense.</p>
|
||||
<h2 id="perspectives-self-and-other"><a class="header" href="#perspectives-self-and-other">Perspectives: Self and Other</a></h2>
|
||||
<p>Real relationships are not symmetric. How one person sees the relationship may differ from how the other sees it. Storybook handles this with <code>self</code> and <code>other</code> blocks:</p>
|
||||
<pre><code class="language-storybook">relationship MentorApprentice {
|
||||
Martha as mentor self {
|
||||
patience: 0.8
|
||||
investment_in_student: 0.9
|
||||
} other {
|
||||
sees_potential: 0.85
|
||||
frustration_level: 0.2
|
||||
}
|
||||
|
||||
Elena as apprentice self {
|
||||
dedication: 0.9
|
||||
overwhelmed: 0.4
|
||||
} other {
|
||||
admiration: 0.95
|
||||
desire_to_impress: 0.9
|
||||
}
|
||||
|
||||
bond: 0.85
|
||||
}
|
||||
</code></pre>
|
||||
<p>Reading this:</p>
|
||||
<ul>
|
||||
<li><strong>Martha’s self view</strong>: She feels patient (80%), highly invested in her student</li>
|
||||
<li><strong>Martha’s view of Elena (other)</strong>: Sees high potential (85%) with low frustration (20%)</li>
|
||||
<li><strong>Elena’s self view</strong>: Dedicated (90%) but sometimes overwhelmed (40%)</li>
|
||||
<li><strong>Elena’s view of Martha (other)</strong>: Deep admiration (95%), strong desire to impress (90%)</li>
|
||||
<li><strong>Shared</strong>: Their bond strength is 0.85</li>
|
||||
</ul>
|
||||
<h2 id="prose-in-relationships"><a class="header" href="#prose-in-relationships">Prose in Relationships</a></h2>
|
||||
<p>Relationships can include narrative descriptions for each participant:</p>
|
||||
<pre><code class="language-storybook">relationship MarthaAndGregory {
|
||||
Martha {
|
||||
role: shopkeeper
|
||||
values_loyalty: 0.9
|
||||
|
||||
---perspective
|
||||
Martha appreciates Gregory's unwavering loyalty. He has
|
||||
been buying her sourdough loaf every morning for fifteen
|
||||
years. Their brief daily exchanges about the weather and
|
||||
local gossip are a comforting routine.
|
||||
---
|
||||
}
|
||||
|
||||
Gregory {
|
||||
role: regular_customer
|
||||
always_orders: "sourdough_loaf"
|
||||
|
||||
---perspective
|
||||
Gregory considers Martha's bakery a cornerstone of his
|
||||
daily routine. The bread is excellent, but it is the brief
|
||||
human connection that keeps him coming back. He worries
|
||||
about what would happen if she ever retired.
|
||||
---
|
||||
}
|
||||
|
||||
bond: 0.7
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="multi-party-relationships"><a class="header" href="#multi-party-relationships">Multi-Party Relationships</a></h2>
|
||||
<p>Relationships can involve more than two participants:</p>
|
||||
<pre><code class="language-storybook">relationship BakerFamily {
|
||||
Martha as parent
|
||||
Jane as parent
|
||||
Emma as child
|
||||
|
||||
household: "Baker Residence"
|
||||
family_bond: 0.95
|
||||
dinner_time: 18:00
|
||||
|
||||
---dynamics
|
||||
A loving family running a bakery together. Martha handles
|
||||
the bread, Jane manages pastries, and Emma helps out on
|
||||
weekends while learning the craft.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="asymmetric-awareness"><a class="header" href="#asymmetric-awareness">Asymmetric Awareness</a></h2>
|
||||
<p>Relationships can model situations where one party does not know the relationship exists:</p>
|
||||
<pre><code class="language-storybook">relationship BossAndNewHire {
|
||||
Martha {
|
||||
role: boss
|
||||
aware_of_struggles: false
|
||||
expects: high_quality_work
|
||||
|
||||
---perspective
|
||||
Martha sees the new hire as competent and expects them
|
||||
to learn the bakery routines quickly. She has no idea
|
||||
they are struggling with the early morning schedule.
|
||||
---
|
||||
}
|
||||
|
||||
NewHire {
|
||||
role: employee
|
||||
intimidated: 0.8
|
||||
hides_struggles: true
|
||||
|
||||
---perspective
|
||||
The new hire is in awe of Martha's skill but terrified
|
||||
of disappointing her. They arrive thirty minutes early
|
||||
every day to practice techniques before she gets in.
|
||||
---
|
||||
}
|
||||
|
||||
bond: 0.4
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="institutional-relationships"><a class="header" href="#institutional-relationships">Institutional Relationships</a></h2>
|
||||
<p>Institutions can participate in relationships too:</p>
|
||||
<pre><code class="language-storybook">relationship GuildMembership {
|
||||
Martha as member
|
||||
BakersGuild as organization
|
||||
|
||||
membership_since: "2015-01-01"
|
||||
standing: "good"
|
||||
dues_paid: true
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="building-a-relationship-web"><a class="header" href="#building-a-relationship-web">Building a Relationship Web</a></h2>
|
||||
<p>Multiple relationships create a rich social network:</p>
|
||||
<pre><code class="language-storybook">relationship Marriage {
|
||||
Martha as spouse
|
||||
Jane as spouse
|
||||
bond: 0.9
|
||||
}
|
||||
|
||||
relationship MentorApprentice {
|
||||
Martha as mentor
|
||||
Elena as apprentice
|
||||
bond: 0.85
|
||||
}
|
||||
|
||||
relationship RegularCustomer {
|
||||
Martha as shopkeeper
|
||||
Gregory as customer
|
||||
bond: 0.7
|
||||
}
|
||||
|
||||
relationship Colleagues {
|
||||
Martha as peer
|
||||
NeighborBaker as peer
|
||||
bond: 0.5
|
||||
competitive: true
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<p>Characters have traits, behaviors, and relationships. In <a href="./07-schedules.html">Schedules and Time</a>, you will give them daily routines and time-based activities.</p>
|
||||
<hr />
|
||||
<p><strong>Reference</strong>: For complete relationship syntax, see the <a href="../reference/15-relationships.html">Relationships Reference</a>.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/05-advanced-behaviors.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/07-schedules.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/05-advanced-behaviors.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/07-schedules.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
422
docs/book/tutorial/07-schedules.html
Normal file
422
docs/book/tutorial/07-schedules.html
Normal file
@@ -0,0 +1,422 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Schedules and Time - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="schedules-and-time"><a class="header" href="#schedules-and-time">Schedules and Time</a></h1>
|
||||
<p>Characters live in time. A baker wakes before dawn to prepare dough; a guard patrols during the day shift; an innkeeper serves customers until late. Schedules define these time-based routines.</p>
|
||||
<h2 id="basic-schedules"><a class="header" href="#basic-schedules">Basic Schedules</a></h2>
|
||||
<p>A schedule contains time blocks, each with a time range and an action:</p>
|
||||
<pre><code class="language-storybook">schedule SimpleBaker {
|
||||
block morning_prep {
|
||||
05:00 - 08:00
|
||||
action: baking::prepare_dough
|
||||
}
|
||||
|
||||
block sales {
|
||||
08:00 - 14:00
|
||||
action: baking::serve_customers
|
||||
}
|
||||
|
||||
block cleanup {
|
||||
14:00 - 15:00
|
||||
action: baking::close_shop
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Time ranges use 24-hour clock format (<code>HH:MM</code>). The <code>action</code> field links to a behavior tree that drives the character’s activity during that block.</p>
|
||||
<h2 id="named-blocks"><a class="header" href="#named-blocks">Named Blocks</a></h2>
|
||||
<p>Blocks can have names (like <code>morning_prep</code> above). Named blocks are important for schedule composition – they allow child schedules to override specific blocks by name.</p>
|
||||
<h2 id="linking-schedules-to-characters"><a class="header" href="#linking-schedules-to-characters">Linking Schedules to Characters</a></h2>
|
||||
<p>Characters use the <code>uses schedule</code> clause:</p>
|
||||
<pre><code class="language-storybook">character Baker: Human {
|
||||
uses schedule: SimpleBaker
|
||||
}
|
||||
</code></pre>
|
||||
<p>For multiple schedules:</p>
|
||||
<pre><code class="language-storybook">character Innkeeper: Human {
|
||||
uses schedules: [WeekdaySchedule, WeekendSchedule]
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="temporal-constraints"><a class="header" href="#temporal-constraints">Temporal Constraints</a></h2>
|
||||
<p>Blocks can be restricted to specific times using temporal constraints:</p>
|
||||
<h3 id="season-constraints"><a class="header" href="#season-constraints">Season Constraints</a></h3>
|
||||
<pre><code class="language-storybook">schedule SeasonalBaker {
|
||||
block summer_hours {
|
||||
06:00 - 20:00
|
||||
action: baking::long_shift
|
||||
on season summer
|
||||
}
|
||||
|
||||
block winter_hours {
|
||||
07:00 - 18:00
|
||||
action: baking::short_shift
|
||||
on season winter
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="day-of-week-constraints"><a class="header" href="#day-of-week-constraints">Day of Week Constraints</a></h3>
|
||||
<pre><code class="language-storybook">schedule WeeklyPattern {
|
||||
block weekday_work {
|
||||
09:00 - 17:00
|
||||
action: work::standard
|
||||
on day monday
|
||||
}
|
||||
|
||||
block weekend_rest {
|
||||
10:00 - 16:00
|
||||
action: leisure::relax
|
||||
on day saturday
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Temporal constraint values (like <code>summer</code> or <code>monday</code>) reference enums defined in your storybook:</p>
|
||||
<pre><code class="language-storybook">enum Season { spring, summer, fall, winter }
|
||||
enum DayOfWeek { monday, tuesday, wednesday, thursday, friday, saturday, sunday }
|
||||
</code></pre>
|
||||
<h2 id="recurring-events"><a class="header" href="#recurring-events">Recurring Events</a></h2>
|
||||
<p>Use <code>recurs</code> to define events that repeat on a schedule:</p>
|
||||
<pre><code class="language-storybook">schedule MarketSchedule {
|
||||
// Regular daily hours
|
||||
block work {
|
||||
08:00 - 17:00
|
||||
action: shop::regular_sales
|
||||
}
|
||||
|
||||
// Market day every Saturday
|
||||
recurs MarketDay on day saturday {
|
||||
block setup {
|
||||
06:00 - 08:00
|
||||
action: market::setup_stall
|
||||
}
|
||||
|
||||
block busy_market {
|
||||
08:00 - 18:00
|
||||
action: market::busy_sales
|
||||
}
|
||||
|
||||
block teardown {
|
||||
18:00 - 20:00
|
||||
action: market::pack_up
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Recurrences take priority over regular blocks. On Saturdays, the <code>MarketDay</code> blocks replace the regular <code>work</code> block.</p>
|
||||
<h2 id="schedule-composition-with-extends"><a class="header" href="#schedule-composition-with-extends">Schedule Composition with extends</a></h2>
|
||||
<p>Schedules can extend other schedules, inheriting and overriding blocks:</p>
|
||||
<pre><code class="language-storybook">schedule BaseShopkeeper {
|
||||
block open {
|
||||
09:00 - 17:00
|
||||
action: shop::standard_hours
|
||||
}
|
||||
}
|
||||
|
||||
schedule EarlyBaker extends BaseShopkeeper {
|
||||
block open {
|
||||
05:00 - 13:00
|
||||
action: baking::early_shift
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>The <code>EarlyBaker</code> schedule overrides the <code>open</code> block by name – same block name, different hours. Any blocks not overridden are inherited unchanged.</p>
|
||||
<p>You can chain extensions:</p>
|
||||
<pre><code class="language-storybook">schedule MasterBaker extends EarlyBaker {
|
||||
block open {
|
||||
03:00 - 11:00
|
||||
action: baking::master_work
|
||||
}
|
||||
|
||||
block teaching {
|
||||
14:00 - 16:00
|
||||
action: baking::teach_apprentice
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p><code>MasterBaker</code> overrides <code>open</code> again and adds a new <code>teaching</code> block.</p>
|
||||
<h2 id="overnight-blocks"><a class="header" href="#overnight-blocks">Overnight Blocks</a></h2>
|
||||
<p>Time ranges can span midnight:</p>
|
||||
<pre><code class="language-storybook">schedule NightGuard {
|
||||
block night_patrol {
|
||||
22:00 - 06:00
|
||||
action: security::patrol
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>The system interprets this as 22:00 to midnight on day one, then midnight to 06:00 on day two.</p>
|
||||
<h2 id="a-complete-schedule-example"><a class="header" href="#a-complete-schedule-example">A Complete Schedule Example</a></h2>
|
||||
<pre><code class="language-storybook">schedule MasterBaker_FullYear {
|
||||
// Daily base
|
||||
block prep {
|
||||
04:00 - 06:00
|
||||
action: baking::prepare
|
||||
}
|
||||
|
||||
block baking {
|
||||
06:00 - 10:00
|
||||
action: baking::bake
|
||||
}
|
||||
|
||||
block sales {
|
||||
10:00 - 16:00
|
||||
action: baking::serve
|
||||
}
|
||||
|
||||
block cleanup {
|
||||
16:00 - 17:00
|
||||
action: baking::clean
|
||||
}
|
||||
|
||||
// Summer extended hours
|
||||
block summer_sales {
|
||||
10:00 - 20:00
|
||||
action: baking::busy_summer
|
||||
on season summer
|
||||
}
|
||||
|
||||
// Weekly market
|
||||
recurs MarketDay on day saturday {
|
||||
block market_prep {
|
||||
02:00 - 04:00
|
||||
action: baking::market_prep
|
||||
}
|
||||
|
||||
block market_sales {
|
||||
08:00 - 18:00
|
||||
action: baking::market_rush
|
||||
}
|
||||
}
|
||||
|
||||
// Annual harvest festival
|
||||
recurs HarvestFestival on dates "Sep 20" .. "Sep 25" {
|
||||
block festival {
|
||||
06:00 - 23:00
|
||||
action: baking::festival_mode
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<p>Characters now have traits, behaviors, relationships, and schedules. In <a href="./08-life-arcs.html">Life Arcs</a>, you will learn how to model character development over time – how they grow, change, and evolve through different phases of life.</p>
|
||||
<hr />
|
||||
<p><strong>Reference</strong>: For complete schedule syntax, see the <a href="../reference/14-schedules.html">Schedules Reference</a>.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/06-relationships.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/08-life-arcs.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/06-relationships.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/08-life-arcs.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
451
docs/book/tutorial/08-life-arcs.html
Normal file
451
docs/book/tutorial/08-life-arcs.html
Normal file
@@ -0,0 +1,451 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Life Arcs - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="life-arcs"><a class="header" href="#life-arcs">Life Arcs</a></h1>
|
||||
<p>Characters grow and change. A timid apprentice becomes a confident master baker. A new employee finds their place in the team. Life arcs model these transformations as state machines – discrete phases with conditions that trigger transitions between them.</p>
|
||||
<h2 id="what-is-a-life-arc"><a class="header" href="#what-is-a-life-arc">What is a Life Arc?</a></h2>
|
||||
<p>A life arc defines:</p>
|
||||
<ul>
|
||||
<li><strong>States</strong>: Distinct phases (e.g., “apprentice”, “journeyman”, “master”)</li>
|
||||
<li><strong>Transitions</strong>: Conditions that move between states (e.g., “when skill > 80, become master”)</li>
|
||||
<li><strong>On-enter actions</strong>: Changes that happen when entering a state (e.g., set confidence to 0.9)</li>
|
||||
</ul>
|
||||
<h2 id="your-first-life-arc"><a class="header" href="#your-first-life-arc">Your First Life Arc</a></h2>
|
||||
<pre><code class="language-storybook">life_arc BakerCareer {
|
||||
state apprentice {
|
||||
on enter {
|
||||
Baker.skill_level: 0.2
|
||||
Baker.confidence: 0.3
|
||||
}
|
||||
|
||||
on skill_level > 0.5 -> journeyman
|
||||
}
|
||||
|
||||
state journeyman {
|
||||
on enter {
|
||||
Baker.confidence: 0.6
|
||||
}
|
||||
|
||||
on skill_level > 0.8 -> master
|
||||
}
|
||||
|
||||
state master {
|
||||
on enter {
|
||||
Baker.confidence: 0.95
|
||||
Baker.can_teach: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Reading this as a story:</p>
|
||||
<blockquote>
|
||||
<p>The baker starts as an <strong>apprentice</strong> with low skill and confidence. When skill exceeds 0.5, they become a <strong>journeyman</strong> with improved confidence. When skill exceeds 0.8, they become a <strong>master</strong> who is confident and can teach others.</p>
|
||||
</blockquote>
|
||||
<h2 id="states"><a class="header" href="#states">States</a></h2>
|
||||
<p>Each state represents a distinct phase. States contain:</p>
|
||||
<ul>
|
||||
<li><strong>on enter block</strong>: Field updates that happen when entering the state</li>
|
||||
<li><strong>Transitions</strong>: Conditions that trigger moving to another state</li>
|
||||
<li><strong>Prose blocks</strong>: Narrative descriptions</li>
|
||||
</ul>
|
||||
<pre><code class="language-storybook">state early_apprentice {
|
||||
on enter {
|
||||
Elena.skill_level: novice
|
||||
Elena.confidence: timid
|
||||
}
|
||||
|
||||
on recipes_mastered > 5 -> growing_apprentice
|
||||
on quit_apprenticeship -> former_apprentice
|
||||
|
||||
---narrative
|
||||
Elena's hands shake as she measures flour. She checks the
|
||||
recipe three times before adding each ingredient. Martha
|
||||
patiently corrects her technique.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="transitions"><a class="header" href="#transitions">Transitions</a></h2>
|
||||
<p>Transitions use expressions to decide when to change state:</p>
|
||||
<pre><code class="language-storybook">// Simple conditions
|
||||
on health < 20 -> fleeing
|
||||
on quest_complete -> celebrating
|
||||
|
||||
// Boolean fields
|
||||
on is_hostile -> combat
|
||||
on not ready -> waiting
|
||||
|
||||
// Equality checks
|
||||
on status is active -> active_state
|
||||
on name is "Martha" -> found_martha
|
||||
|
||||
// Complex conditions
|
||||
on health < 50 and enemy_count > 3 -> retreat
|
||||
on (tired and hungry) or exhausted -> resting
|
||||
</code></pre>
|
||||
<h3 id="transition-priority"><a class="header" href="#transition-priority">Transition Priority</a></h3>
|
||||
<p>When multiple transitions could fire, the <strong>first one in declaration order</strong> wins:</p>
|
||||
<pre><code class="language-storybook">state combat {
|
||||
on health < 10 -> desperate // Checked first
|
||||
on health < 50 -> defensive // Checked second
|
||||
on surrounded -> retreat // Checked third
|
||||
}
|
||||
</code></pre>
|
||||
<p>If health is 5, only <code>desperate</code> triggers, even though <code>defensive</code> is also true.</p>
|
||||
<h2 id="on-enter-actions"><a class="header" href="#on-enter-actions">On-Enter Actions</a></h2>
|
||||
<p>The <code>on enter</code> block sets field values when entering a state. It runs exactly once per transition:</p>
|
||||
<pre><code class="language-storybook">state master_baker {
|
||||
on enter {
|
||||
Elena.skill_level: master
|
||||
Elena.confidence: commanding
|
||||
Elena.can_teach: true
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>The syntax is <code>EntityName.field: value</code>. This updates the referenced character’s field.</p>
|
||||
<h2 id="elenas-complete-journey"><a class="header" href="#elenas-complete-journey">Elena’s Complete Journey</a></h2>
|
||||
<p>Here is a complete life arc tracking Elena’s growth from apprentice to master:</p>
|
||||
<pre><code class="language-storybook">life_arc ElenaCareer {
|
||||
---description
|
||||
Tracks Elena's professional and personal growth as she
|
||||
progresses from nervous apprentice to confident master baker.
|
||||
---
|
||||
|
||||
state early_apprentice {
|
||||
on enter {
|
||||
Elena.skill_level: novice
|
||||
Elena.confidence: timid
|
||||
}
|
||||
|
||||
on recipes_mastered > 5 -> 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 > 15 -> 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 > 50 -> master
|
||||
|
||||
---narrative
|
||||
Elena runs the morning shift alone while Martha handles
|
||||
special orders. Customers start asking for "Elena's rolls."
|
||||
---
|
||||
}
|
||||
|
||||
state master {
|
||||
on enter {
|
||||
Elena.skill_level: master
|
||||
Elena.confidence: commanding
|
||||
Elena.can_teach: true
|
||||
}
|
||||
|
||||
---narrative
|
||||
Master Baker Elena. She has earned it. The guild acknowledges
|
||||
her mastery, and Martha beams with pride. Elena begins
|
||||
mentoring her own apprentice.
|
||||
---
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="common-patterns"><a class="header" href="#common-patterns">Common Patterns</a></h2>
|
||||
<h3 id="hub-and-spoke"><a class="header" href="#hub-and-spoke">Hub-and-Spoke</a></h3>
|
||||
<p>A central idle state with branches to specialized states:</p>
|
||||
<pre><code class="language-storybook">life_arc BakerBehavior {
|
||||
state idle {
|
||||
on customer_arrived -> serving
|
||||
on order_placed -> baking
|
||||
on delivery_arrived -> receiving
|
||||
}
|
||||
|
||||
state serving { on customer_served -> idle }
|
||||
state baking { on batch_complete -> idle }
|
||||
state receiving { on delivery_processed -> idle }
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="linear-progression"><a class="header" href="#linear-progression">Linear Progression</a></h3>
|
||||
<p>States form a one-way sequence (quests, tutorials):</p>
|
||||
<pre><code class="language-storybook">life_arc Tutorial {
|
||||
state intro { on clicked_start -> movement }
|
||||
state movement { on moved_forward -> combat }
|
||||
state combat { on defeated_enemy -> inventory }
|
||||
state inventory { on opened_inventory -> complete }
|
||||
state complete {}
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="cyclic-states"><a class="header" href="#cyclic-states">Cyclic States</a></h3>
|
||||
<p>States form a loop (day/night, seasons, mood swings):</p>
|
||||
<pre><code class="language-storybook">life_arc DayNightCycle {
|
||||
state dawn { on hour >= 8 -> day }
|
||||
state day { on hour >= 18 -> dusk }
|
||||
state dusk { on hour >= 20 -> night }
|
||||
state night { on hour >= 6 -> dawn }
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="tips"><a class="header" href="#tips">Tips</a></h2>
|
||||
<p><strong>Order transitions by urgency</strong>: Put the most critical conditions first, since the first true transition wins.</p>
|
||||
<p><strong>Use descriptive state names</strong>: <code>waiting_for_customer</code> is clearer than <code>state1</code>.</p>
|
||||
<p><strong>Add narrative prose blocks</strong>: They make life arcs readable as stories and serve as documentation.</p>
|
||||
<p><strong>Avoid orphan states</strong>: Every state should be reachable from some other state (the compiler will warn you about unreachable states).</p>
|
||||
<h2 id="what-you-have-learned"><a class="header" href="#what-you-have-learned">What You Have Learned</a></h2>
|
||||
<p>Congratulations! You have completed the tutorial. You now know how to:</p>
|
||||
<ul>
|
||||
<li>Create characters with species, templates, and enums</li>
|
||||
<li>Build behavior trees with selectors, sequences, and decorators</li>
|
||||
<li>Add conditions and action parameters</li>
|
||||
<li>Model relationships with perspectives</li>
|
||||
<li>Define time-based schedules</li>
|
||||
<li>Track character development with life arcs</li>
|
||||
</ul>
|
||||
<h2 id="where-to-go-next"><a class="header" href="#where-to-go-next">Where to Go Next</a></h2>
|
||||
<ul>
|
||||
<li><strong><a href="../reference/09-overview.html">Reference Guide</a></strong>: Complete syntax specifications</li>
|
||||
<li><strong><a href="../advanced/20-patterns.html">Design Patterns</a></strong>: Common patterns and best practices</li>
|
||||
<li><strong><a href="../examples/24-baker-family-complete.html">Examples Gallery</a></strong>: Full working examples to learn from</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<p><strong>Reference</strong>: For complete life arc syntax, see the <a href="../reference/13-life-arcs.html">Life Arcs Reference</a>.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/07-schedules.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/09-locations-institutions.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/07-schedules.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../tutorial/09-locations-institutions.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
635
docs/book/tutorial/09-locations-institutions.html
Normal file
635
docs/book/tutorial/09-locations-institutions.html
Normal file
@@ -0,0 +1,635 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Locations and Institutions - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="locations-and-institutions"><a class="header" href="#locations-and-institutions">Locations and Institutions</a></h1>
|
||||
<p>So far you have created characters, given them behaviors, connected them with relationships, scheduled their days, and guided them through life arcs. But characters need places to <em>be</em> and organizations to <em>belong to</em>. That is what locations and institutions provide.</p>
|
||||
<hr />
|
||||
<h2 id="what-are-locations"><a class="header" href="#what-are-locations">What Are Locations?</a></h2>
|
||||
<p>A <strong>location</strong> is a place in your world. It can be a building, a room, a park, a city – any space where things happen. Locations hold fields that describe the place and optional prose blocks for narrative detail.</p>
|
||||
<h3 id="your-first-location"><a class="header" href="#your-first-location">Your First Location</a></h3>
|
||||
<p>Let’s create the bakery where Martha works:</p>
|
||||
<pre><code class="language-storybook">location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
}
|
||||
</code></pre>
|
||||
<p>That is all it takes. The <code>location</code> keyword, a name, and a block of fields. Every field is a key-value pair, and you choose whatever fields make sense for your world.</p>
|
||||
<h3 id="adding-detail"><a class="header" href="#adding-detail">Adding Detail</a></h3>
|
||||
<p>A real location needs more than three fields. Let’s flesh out the bakery:</p>
|
||||
<pre><code class="language-storybook">location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
employees: 4
|
||||
specialty: "artisan sourdough"
|
||||
daily_output_loaves: 80..120
|
||||
open: true
|
||||
established: "2011"
|
||||
}
|
||||
</code></pre>
|
||||
<p>Notice <code>daily_output_loaves: 80..120</code> – that is a range. Each simulation run can pick a different number of loaves, adding natural variation.</p>
|
||||
<h3 id="prose-blocks"><a class="header" href="#prose-blocks">Prose Blocks</a></h3>
|
||||
<p>Bare fields are good for data, but locations also need narrative flavor. Use prose blocks:</p>
|
||||
<pre><code class="language-storybook">location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
|
||||
---description
|
||||
A warm, inviting bakery on Main Street. The smell of fresh bread
|
||||
wafts out the door every morning at dawn. Martha has run the shop
|
||||
for fifteen years, and the locals consider it the heart of the
|
||||
neighborhood.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<p>Prose blocks start with <code>---tag_name</code> and end with <code>---</code>. The tag name (<code>description</code> here) becomes the key. You can have as many prose blocks as you want:</p>
|
||||
<pre><code class="language-storybook">location BakersBakery {
|
||||
type: bakery
|
||||
|
||||
---description
|
||||
The bakery on Main Street...
|
||||
---
|
||||
|
||||
---history
|
||||
Originally a hardware store, Martha converted the space in 2011...
|
||||
---
|
||||
|
||||
---atmosphere
|
||||
Flour dust catches the light from tall windows...
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<hr />
|
||||
<h2 id="building-a-world-with-locations"><a class="header" href="#building-a-world-with-locations">Building a World with Locations</a></h2>
|
||||
<p>Locations work best when they form a coherent world. Here is the Baker family’s neighborhood:</p>
|
||||
<pre><code class="language-storybook">location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
owner: Martha
|
||||
|
||||
---description
|
||||
Martha's artisan bakery. The stone oven was imported from France.
|
||||
---
|
||||
}
|
||||
|
||||
location BakerHome {
|
||||
type: residence
|
||||
address: "22 Elm Lane"
|
||||
bedrooms: 4
|
||||
has_garden: true
|
||||
|
||||
---description
|
||||
The Baker family home. Martha insisted on an oversized kitchen.
|
||||
---
|
||||
}
|
||||
|
||||
location BakersGuildHall {
|
||||
type: guild_hall
|
||||
address: "7 Guild Row"
|
||||
capacity: 100
|
||||
established: "1892"
|
||||
|
||||
---description
|
||||
The historic headquarters of the Bakers Guild.
|
||||
---
|
||||
}
|
||||
|
||||
location TownSquare {
|
||||
type: public_square
|
||||
capacity: 500
|
||||
has_fountain: true
|
||||
has_market_stalls: true
|
||||
|
||||
---description
|
||||
The central gathering place. On weekends, the farmers market
|
||||
fills the square with produce stalls.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="modeling-hierarchy"><a class="header" href="#modeling-hierarchy">Modeling Hierarchy</a></h3>
|
||||
<p>Storybook does not enforce a built-in parent-child relationship for locations. Instead, you use fields to express hierarchy:</p>
|
||||
<pre><code class="language-storybook">location MainStreet {
|
||||
type: street
|
||||
district: TownCenter
|
||||
shops: 12
|
||||
}
|
||||
|
||||
location BakersBakery {
|
||||
type: bakery
|
||||
street: MainStreet
|
||||
district: TownCenter
|
||||
}
|
||||
</code></pre>
|
||||
<p>This convention-based approach keeps the language simple while letting you model whatever spatial relationships your world needs.</p>
|
||||
<hr />
|
||||
<h2 id="what-are-institutions"><a class="header" href="#what-are-institutions">What Are Institutions?</a></h2>
|
||||
<p>An <strong>institution</strong> is an organization, group, or system. Think of it as a character that represents a collective: a guild, a government, a school, a business. Institutions have a key capability that locations lack – they can <strong>use behaviors and schedules</strong>, just like characters.</p>
|
||||
<h3 id="your-first-institution"><a class="header" href="#your-first-institution">Your First Institution</a></h3>
|
||||
<pre><code class="language-storybook">institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
founded: "1892"
|
||||
reputation: 0.85
|
||||
}
|
||||
</code></pre>
|
||||
<p>This looks just like a location so far. The difference comes when you add behaviors.</p>
|
||||
<h3 id="institutions-with-behaviors"><a class="header" href="#institutions-with-behaviors">Institutions with Behaviors</a></h3>
|
||||
<p>Institutions can act. The <code>uses behaviors</code> clause links behavior trees to the institution:</p>
|
||||
<pre><code class="language-storybook">institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
reputation: 0.85
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices },
|
||||
{ tree: NegotiateSuppliers },
|
||||
{ tree: HostEvents }
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
<p>Each entry in the list is a behavior link object with a <code>tree</code> field. This tells the simulation engine that the Bakers Guild can manage apprentices, negotiate with suppliers, and host events.</p>
|
||||
<h3 id="behavior-priorities"><a class="header" href="#behavior-priorities">Behavior Priorities</a></h3>
|
||||
<p>Not all behaviors are equally important. Use the <code>priority</code> field:</p>
|
||||
<pre><code class="language-storybook">institution BakersGuild {
|
||||
type: trade_guild
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices, priority: normal },
|
||||
{ tree: NegotiateSuppliers, priority: high },
|
||||
{ tree: HostEvents, priority: low }
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
<p>Priority levels are <code>low</code>, <code>normal</code>, <code>high</code>, and <code>critical</code>. Higher-priority behaviors take precedence when the institution must choose between actions.</p>
|
||||
<h3 id="conditional-behaviors"><a class="header" href="#conditional-behaviors">Conditional Behaviors</a></h3>
|
||||
<p>Some behaviors only activate under certain conditions:</p>
|
||||
<pre><code class="language-storybook">institution BakersGuild {
|
||||
type: trade_guild
|
||||
reputation: 0.85
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices },
|
||||
{ tree: NegotiateSuppliers, priority: high },
|
||||
{ tree: EmergencyMeeting, when: reputation < 0.3, priority: critical }
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
<p>The <code>when</code> clause uses an <a href="../reference/17-expressions.html">expression</a>. Here, the emergency meeting behavior only activates when reputation drops below 0.3.</p>
|
||||
<h3 id="institutions-with-schedules"><a class="header" href="#institutions-with-schedules">Institutions with Schedules</a></h3>
|
||||
<p>Institutions can also follow schedules:</p>
|
||||
<pre><code class="language-storybook">institution BakersGuild {
|
||||
type: trade_guild
|
||||
uses schedule: GuildOperatingHours
|
||||
}
|
||||
</code></pre>
|
||||
<p>For multiple schedules:</p>
|
||||
<pre><code class="language-storybook">institution BakersGuild {
|
||||
type: trade_guild
|
||||
uses schedules: [WeekdaySchedule, WeekendSchedule]
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="prose-blocks-1"><a class="header" href="#prose-blocks-1">Prose Blocks</a></h3>
|
||||
<p>Just like locations, institutions support prose blocks:</p>
|
||||
<pre><code class="language-storybook">institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
|
||||
---description
|
||||
The Bakers Guild has been the backbone of the town's bread trade
|
||||
since 1892. Members share recipes, arrange apprenticeships, and
|
||||
collectively negotiate flour prices.
|
||||
---
|
||||
|
||||
---charter
|
||||
Article I: All members shall maintain the highest standards.
|
||||
Article II: Apprentices must complete a three-year program.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<hr />
|
||||
<h2 id="connecting-characters-to-institutions"><a class="header" href="#connecting-characters-to-institutions">Connecting Characters to Institutions</a></h2>
|
||||
<p>Institutions do not have a built-in membership list. You model membership through character fields or relationships.</p>
|
||||
<h3 id="through-character-fields"><a class="header" href="#through-character-fields">Through Character Fields</a></h3>
|
||||
<p>The simplest approach – add fields to your characters:</p>
|
||||
<pre><code class="language-storybook">character Martha {
|
||||
age: 45
|
||||
occupation: baker
|
||||
guild: BakersGuild
|
||||
guild_role: guild_master
|
||||
guild_member_since: "2005"
|
||||
}
|
||||
|
||||
character Jane {
|
||||
age: 19
|
||||
occupation: apprentice_baker
|
||||
guild: BakersGuild
|
||||
guild_role: apprentice
|
||||
guild_member_since: "2024"
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="through-relationships"><a class="header" href="#through-relationships">Through Relationships</a></h3>
|
||||
<p>For richer modeling, use relationships:</p>
|
||||
<pre><code class="language-storybook">relationship GuildMembership {
|
||||
Martha as guild_master { years_active: 20 }
|
||||
BakersGuild as organization { }
|
||||
bond: 0.95
|
||||
}
|
||||
|
||||
relationship Apprenticeship {
|
||||
Jane as apprentice { skills_learned: 12 }
|
||||
Martha as mentor { patience_remaining: 0.7 }
|
||||
BakersGuild as guild { }
|
||||
years_completed: 1
|
||||
}
|
||||
</code></pre>
|
||||
<p>This approach captures richer information: roles, duration, and multi-party connections.</p>
|
||||
<hr />
|
||||
<h2 id="locations-vs-institutions"><a class="header" href="#locations-vs-institutions">Locations vs. Institutions</a></h2>
|
||||
<p>When should you use each?</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th>Question</th><th>Use…</th></tr></thead><tbody>
|
||||
<tr><td>Where does something happen?</td><td>Location</td></tr>
|
||||
<tr><td>Who or what organizes things?</td><td>Institution</td></tr>
|
||||
<tr><td>Does it need behaviors?</td><td>Institution</td></tr>
|
||||
<tr><td>Does it need a schedule?</td><td>Institution</td></tr>
|
||||
<tr><td>Is it purely a place?</td><td>Location</td></tr>
|
||||
<tr><td>Is it a group or organization?</td><td>Institution</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<p>Sometimes the same concept needs both:</p>
|
||||
<pre><code class="language-storybook">// The physical building
|
||||
location BakersGuildHall {
|
||||
type: guild_hall
|
||||
address: "7 Guild Row"
|
||||
capacity: 100
|
||||
}
|
||||
|
||||
// The organization that meets there
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
location: BakersGuildHall
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices },
|
||||
{ tree: NegotiateSuppliers }
|
||||
]
|
||||
|
||||
uses schedule: GuildOperatingHours
|
||||
}
|
||||
</code></pre>
|
||||
<p>The guild hall is a <em>place</em>. The guild is an <em>organization</em>. Keeping them separate lets you say “the guild meets at the guild hall” without conflating the building with the institution.</p>
|
||||
<hr />
|
||||
<h2 id="putting-it-all-together"><a class="header" href="#putting-it-all-together">Putting It All Together</a></h2>
|
||||
<p>Here is a complete example showing how locations, institutions, and characters work together in the Baker family world:</p>
|
||||
<pre><code class="language-storybook">// Enums for type safety
|
||||
enum PlaceType {
|
||||
bakery, residence, guild_hall, public_square
|
||||
}
|
||||
|
||||
enum GuildRole {
|
||||
guild_master, journeyman, apprentice
|
||||
}
|
||||
|
||||
// Locations: where things happen
|
||||
location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
owner: Martha
|
||||
|
||||
---description
|
||||
Martha's artisan bakery on Main Street.
|
||||
---
|
||||
}
|
||||
|
||||
location BakerHome {
|
||||
type: residence
|
||||
address: "22 Elm Lane"
|
||||
bedrooms: 4
|
||||
residents: ["Martha", "David", "Jane", "Tom"]
|
||||
}
|
||||
|
||||
location BakersGuildHall {
|
||||
type: guild_hall
|
||||
address: "7 Guild Row"
|
||||
capacity: 100
|
||||
|
||||
---description
|
||||
The historic Bakers Guild headquarters, established 1892.
|
||||
---
|
||||
}
|
||||
|
||||
// Institution: the organization
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
founded: "1892"
|
||||
reputation: 0.85
|
||||
location: BakersGuildHall
|
||||
leader: Martha
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices, priority: normal },
|
||||
{ tree: NegotiateSuppliers, priority: high },
|
||||
{ tree: HostAnnualBakeOff, when: month is october }
|
||||
]
|
||||
|
||||
uses schedule: GuildOperatingHours
|
||||
|
||||
---description
|
||||
The Bakers Guild oversees apprenticeships, quality standards,
|
||||
and the annual Great Bake-Off competition.
|
||||
---
|
||||
}
|
||||
|
||||
// Institution: the business
|
||||
institution BakersBakeryBusiness {
|
||||
type: business
|
||||
owner: Martha
|
||||
employees: 4
|
||||
location: BakersBakery
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: DailyBakingOps, priority: high },
|
||||
{ tree: InventoryManagement }
|
||||
]
|
||||
|
||||
uses schedule: BakeryOperatingHours
|
||||
}
|
||||
|
||||
// Characters connected to all of the above
|
||||
character Martha {
|
||||
age: 45
|
||||
occupation: baker
|
||||
workplace: BakersBakery
|
||||
home: BakerHome
|
||||
guild: BakersGuild
|
||||
guild_role: guild_master
|
||||
}
|
||||
|
||||
character Jane {
|
||||
age: 19
|
||||
occupation: apprentice_baker
|
||||
workplace: BakersBakery
|
||||
home: BakerHome
|
||||
guild: BakersGuild
|
||||
guild_role: apprentice
|
||||
}
|
||||
|
||||
// Relationships tying it all together
|
||||
relationship GuildLeadership {
|
||||
Martha as guild_master { }
|
||||
BakersGuild as guild { }
|
||||
years_in_role: 8
|
||||
}
|
||||
|
||||
relationship BakeryApprenticeship {
|
||||
Jane as apprentice { }
|
||||
Martha as mentor { }
|
||||
BakersGuild as certifying_body { }
|
||||
year: 1
|
||||
total_years: 3
|
||||
}
|
||||
</code></pre>
|
||||
<hr />
|
||||
<h2 id="key-takeaways"><a class="header" href="#key-takeaways">Key Takeaways</a></h2>
|
||||
<ol>
|
||||
<li><strong>Locations</strong> are simple: name, fields, prose blocks. They model <em>places</em>.</li>
|
||||
<li><strong>Institutions</strong> are richer: they add <code>uses behaviors</code> and <code>uses schedule</code> on top of fields and prose. They model <em>organizations</em>.</li>
|
||||
<li><strong>Membership</strong> is modeled through character fields or relationships, not built into institution syntax.</li>
|
||||
<li><strong>Separate place from organization</strong>: A guild hall (location) and the guild (institution) are different things.</li>
|
||||
<li><strong>Use enums</strong> for type-safe categorization of locations and institutions.</li>
|
||||
</ol>
|
||||
<hr />
|
||||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||||
<ul>
|
||||
<li>Learn about <a href="../reference/17-expressions.html">expressions</a> used in conditional behavior links</li>
|
||||
<li>Explore <a href="./03-first-behavior-tree.html">behavior trees</a> to create the behaviors your institutions use</li>
|
||||
<li>See <a href="./07-schedules.html">schedules</a> to define operating hours for institutions</li>
|
||||
<li>Read the full <a href="../reference/16a-locations.html">Locations Reference</a> and <a href="../reference/16b-institutions.html">Institutions Reference</a></li>
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../tutorial/08-life-arcs.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../reference/09-overview.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../tutorial/08-life-arcs.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../reference/09-overview.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user