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
11336 lines
437 KiB
HTML
11336 lines
437 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>Storybook Language Guide</title>
|
||
<meta name="robots" content="noindex">
|
||
|
||
|
||
<!-- 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="storybook-language-guide"><a class="header" href="#storybook-language-guide">Storybook Language Guide</a></h1>
|
||
<blockquote>
|
||
<p><strong>Create rich narrative simulations through code that reads like stories.</strong></p>
|
||
</blockquote>
|
||
<p>Welcome to the complete guide for the Storybook narrative simulation language! Whether you’re a creative writer bringing characters to life or a developer building simulation systems, this documentation will help you master Storybook.</p>
|
||
<hr />
|
||
<p><strong>⚠️ Alpha Software Notice</strong></p>
|
||
<p>Storybook is currently in <strong>alpha</strong> and under active development at <a href="https://r3t.io">r3t Studios</a>. While the core language features are stable and ready to use, you should expect:</p>
|
||
<ul>
|
||
<li><strong>New features</strong> to be added as we expand the language capabilities</li>
|
||
<li><strong>Minor syntax adjustments</strong> as we refine the design based on real-world usage</li>
|
||
<li><strong>API changes</strong> in the compiled output format as we optimize for game engine integration</li>
|
||
</ul>
|
||
<p>We’re committed to a thoughtful path toward version 1.0. Breaking changes will be clearly documented, and we’ll provide migration guides when syntax evolves. Your feedback during this alpha period is invaluable in shaping the language’s future!</p>
|
||
<hr />
|
||
<h2 id="what-is-storybook"><a class="header" href="#what-is-storybook">What is Storybook?</a></h2>
|
||
<p>Storybook is a <strong>compiled simulation language</strong> designed for <strong>open-world, autonomous game simulations</strong>. While it includes a basic embedded virtual machine for terminal-based debugging, it’s built to be integrated into game engines and developed hand-in-hand with technical game developers.</p>
|
||
<p>Storybook defines characters, behaviors, relationships, and narrative events for autonomous agents in dynamic worlds. It bridges the gap between storytelling and technical simulation through:</p>
|
||
<ul>
|
||
<li><strong>Readable syntax</strong> - Code that looks like natural descriptions, but compiles to efficient bytecode</li>
|
||
<li><strong>Named nodes</strong> - Behavior trees you can read as stories, that drive AI decision-making</li>
|
||
<li><strong>Prose blocks</strong> - Embed narrative directly in definitions for context-aware storytelling</li>
|
||
<li><strong>Rich semantics</strong> - From simple traits to complex state machines and schedules</li>
|
||
<li><strong>Game engine integration</strong> - Designed to power autonomous NPCs in Unity, Unreal, Godot, and custom engines</li>
|
||
</ul>
|
||
<h2 id="choose-your-path"><a class="header" href="#choose-your-path">Choose Your Path</a></h2>
|
||
<h3 id="-for-storytellers"><a class="header" href="#-for-storytellers">🎨 For Storytellers</a></h3>
|
||
<p><strong>New to programming?</strong> Start with the <a href="tutorial/01-welcome.html">Tutorial Track</a> for a gentle, example-driven introduction. Learn by building a bakery simulation!</p>
|
||
<h3 id="-for-developers"><a class="header" href="#-for-developers">💻 For Developers</a></h3>
|
||
<p><strong>Need technical precision?</strong> Jump to the <a href="reference/09-overview.html">Reference Guide</a> for complete syntax specifications and semantic details.</p>
|
||
<h3 id="-for-everyone"><a class="header" href="#-for-everyone">✨ For Everyone</a></h3>
|
||
<p><strong>Want inspiration?</strong> Browse the <a href="examples/24-baker-family-complete.html">Examples Gallery</a> to see what’s possible!</p>
|
||
<h2 id="quick-start"><a class="header" href="#quick-start">Quick Start</a></h2>
|
||
<pre><code class="language-storybook">character Martha {
|
||
age: 34
|
||
skill_level: 0.95
|
||
|
||
---description
|
||
A master baker who learned from her grandmother
|
||
and now runs the most popular bakery in town.
|
||
---
|
||
}
|
||
|
||
behavior Baker_MorningRoutine {
|
||
choose daily_priority {
|
||
then prepare_sourdough { ... }
|
||
then serve_customers { ... }
|
||
then restock_display { ... }
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><a href="tutorial/01-welcome.html">Continue to Tutorial →</a></p>
|
||
<h2 id="documentation-structure"><a class="header" href="#documentation-structure">Documentation Structure</a></h2>
|
||
<ul>
|
||
<li><strong>Part I: Getting Started</strong> - Tutorials for learning Storybook</li>
|
||
<li><strong>Part II: Complete Reference</strong> - Technical specifications</li>
|
||
<li><strong>Part III: Advanced Topics</strong> - Patterns and integration</li>
|
||
<li><strong>Part IV: Examples Gallery</strong> - Complete working examples</li>
|
||
</ul>
|
||
<h2 id="getting-help"><a class="header" href="#getting-help">Getting Help</a></h2>
|
||
<ul>
|
||
<li><strong>In-Editor</strong>: Hover over keywords for quick help</li>
|
||
<li><strong>Search</strong>: Use the search box (top right) to find anything</li>
|
||
<li><strong>Examples</strong>: Working code is the best teacher!</li>
|
||
</ul>
|
||
<hr />
|
||
<p><strong>Ready to begin?</strong> <a href="tutorial/01-welcome.html">Start with the Tutorial →</a></p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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-1"><a class="header" href="#what-is-storybook-1">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="tutorial/./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="tutorial/../reference/09-overview.html">Reference Guide</a> for precise syntax details.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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-1"><a class="header" href="#next-steps-1">Next Steps</a></h2>
|
||
<p>Now that Martha exists, let us give her something to do. In <a href="tutorial/./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="tutorial/../reference/10-characters.html">Characters Reference</a>.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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="tutorial/./04-making-characters-act.html">Making Characters Act</a>.</p>
|
||
<h2 id="next-steps-2"><a class="header" href="#next-steps-2">Next Steps</a></h2>
|
||
<p>Your behavior trees so far make decisions between options and run sequences of actions. In <a href="tutorial/./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="tutorial/../reference/11-behavior-trees.html">Behavior Trees Reference</a>.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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-1"><a class="header" href="#a-complete-example-1">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-3"><a class="header" href="#next-steps-3">Next Steps</a></h2>
|
||
<p>You now know the full toolkit for behavior trees. In <a href="tutorial/./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="tutorial/../reference/12-decorators.html">Decorators Reference</a> for all decorator types and <a href="tutorial/../reference/17-expressions.html">Expression Language</a> for complete condition syntax.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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-4"><a class="header" href="#next-steps-4">Next Steps</a></h2>
|
||
<p>Characters do not exist in isolation. In <a href="tutorial/./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="tutorial/../reference/11-behavior-trees.html">Behavior Trees Reference</a> and <a href="tutorial/../reference/12-decorators.html">Decorators Reference</a> for complete syntax.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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-5"><a class="header" href="#next-steps-5">Next Steps</a></h2>
|
||
<p>Characters have traits, behaviors, and relationships. In <a href="tutorial/./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="tutorial/../reference/15-relationships.html">Relationships Reference</a>.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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-6"><a class="header" href="#next-steps-6">Next Steps</a></h2>
|
||
<p>Characters now have traits, behaviors, relationships, and schedules. In <a href="tutorial/./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="tutorial/../reference/14-schedules.html">Schedules Reference</a>.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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="tutorial/../reference/09-overview.html">Reference Guide</a></strong>: Complete syntax specifications</li>
|
||
<li><strong><a href="tutorial/../advanced/20-patterns.html">Design Patterns</a></strong>: Common patterns and best practices</li>
|
||
<li><strong><a href="tutorial/../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="tutorial/../reference/13-life-arcs.html">Life Arcs Reference</a>.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><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="tutorial/../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-1"><a class="header" href="#putting-it-all-together-1">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-7"><a class="header" href="#next-steps-7">Next Steps</a></h2>
|
||
<ul>
|
||
<li>Learn about <a href="tutorial/../reference/17-expressions.html">expressions</a> used in conditional behavior links</li>
|
||
<li>Explore <a href="tutorial/./03-first-behavior-tree.html">behavior trees</a> to create the behaviors your institutions use</li>
|
||
<li>See <a href="tutorial/./07-schedules.html">schedules</a> to define operating hours for institutions</li>
|
||
<li>Read the full <a href="tutorial/../reference/16a-locations.html">Locations Reference</a> and <a href="tutorial/../reference/16b-institutions.html">Institutions Reference</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="language-overview"><a class="header" href="#language-overview">Language Overview</a></h1>
|
||
<blockquote>
|
||
<p><strong>The Storybook language enables narrative simulation through structured declarations of characters, behaviors, relationships, and events.</strong></p>
|
||
</blockquote>
|
||
<h2 id="philosophy"><a class="header" href="#philosophy">Philosophy</a></h2>
|
||
<p>Storybook is a domain-specific language for narrative simulation, influenced by:</p>
|
||
<ul>
|
||
<li><strong>Rust</strong>: Strong typing, explicit declarations, and clear ownership semantics</li>
|
||
<li><strong>C#</strong>: Object-oriented patterns with declarative syntax</li>
|
||
<li><strong>Python</strong>: Readable, accessible syntax that prioritizes clarity</li>
|
||
</ul>
|
||
<p>The language balances <strong>technical precision</strong> with <strong>narrative expressiveness</strong>, making it accessible to storytellers while maintaining the rigor developers need.</p>
|
||
<h2 id="design-principles"><a class="header" href="#design-principles">Design Principles</a></h2>
|
||
<h3 id="1-code-as-narrative"><a class="header" href="#1-code-as-narrative">1. Code as Narrative</a></h3>
|
||
<p>Named nodes and prose blocks let code read like stories:</p>
|
||
<pre><code class="language-storybook">behavior Baker_MorningRoutine {
|
||
choose daily_priority {
|
||
then prepare_sourdough { ... }
|
||
then serve_customers { ... }
|
||
then restock_display { ... }
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="2-explicit-is-better-than-implicit"><a class="header" href="#2-explicit-is-better-than-implicit">2. Explicit is Better Than Implicit</a></h3>
|
||
<p>Every declaration is self-documenting:</p>
|
||
<ul>
|
||
<li>Character fields show what defines them</li>
|
||
<li>Behavior trees show decision structures</li>
|
||
<li>Relationships name their participants</li>
|
||
</ul>
|
||
<h3 id="3-progressive-disclosure"><a class="header" href="#3-progressive-disclosure">3. Progressive Disclosure</a></h3>
|
||
<p>Simple cases are simple, complex cases are possible:</p>
|
||
<ul>
|
||
<li>Basic characters need just a name and fields</li>
|
||
<li>Templates enable inheritance and reuse</li>
|
||
<li>Advanced features (state machines, decorators) available when needed</li>
|
||
</ul>
|
||
<h3 id="4-semantic-validation"><a class="header" href="#4-semantic-validation">4. Semantic Validation</a></h3>
|
||
<p>The compiler catches narrative errors:</p>
|
||
<ul>
|
||
<li>Bond values must be 0.0..1.0</li>
|
||
<li>Schedule blocks can’t overlap</li>
|
||
<li>Life arc transitions must reference valid states</li>
|
||
</ul>
|
||
<h2 id="language-structure"><a class="header" href="#language-structure">Language Structure</a></h2>
|
||
<h3 id="declaration-types"><a class="header" href="#declaration-types">Declaration Types</a></h3>
|
||
<p>Storybook has 10 top-level declaration types:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Declaration</th><th>Purpose</th><th>Example</th></tr></thead><tbody>
|
||
<tr><td><code>character</code></td><td>Define entities with traits and behaviors</td><td>A baker with skills and schedule</td></tr>
|
||
<tr><td><code>template</code></td><td>Reusable patterns with ranges</td><td>A generic NPC template</td></tr>
|
||
<tr><td><code>behavior</code></td><td>Decision trees for actions</td><td>How a character responds to events</td></tr>
|
||
<tr><td><code>life_arc</code></td><td>State machines for life stages</td><td>Apprentice → Baker → Master</td></tr>
|
||
<tr><td><code>schedule</code></td><td>Time-based activities</td><td>Daily routine from 6am to 10pm</td></tr>
|
||
<tr><td><code>relationship</code></td><td>Connections between entities</td><td>Parent-child with bond values</td></tr>
|
||
<tr><td><code>institution</code></td><td>Organizations and groups</td><td>A bakery with employees</td></tr>
|
||
<tr><td><code>location</code></td><td>Places with properties</td><td>The town square</td></tr>
|
||
<tr><td><code>species</code></td><td>Type definitions with traits</td><td>Human vs Cat vs Rabbit</td></tr>
|
||
<tr><td><code>enum</code></td><td>Named value sets</td><td>EmotionalState options</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="value-types"><a class="header" href="#value-types">Value Types</a></h3>
|
||
<p>Fields can contain:</p>
|
||
<ul>
|
||
<li><strong>Primitives</strong>: <code>42</code>, <code>3.14</code>, <code>"text"</code>, <code>true</code></li>
|
||
<li><strong>Time</strong>: <code>08:30:00</code>, <code>14:15</code></li>
|
||
<li><strong>Duration</strong>: <code>2h30m</code>, <code>45s</code></li>
|
||
<li><strong>Ranges</strong>: <code>20..40</code> (for templates)</li>
|
||
<li><strong>Identifiers</strong>: <code>OtherCharacter</code>, <code>path::to::Thing</code></li>
|
||
<li><strong>Lists</strong>: <code>[1, 2, 3]</code></li>
|
||
<li><strong>Objects</strong>: <code>{ field: value }</code></li>
|
||
<li><strong>Prose blocks</strong>: <code> ---tag\nMulti-line\ntext\n---</code></li>
|
||
</ul>
|
||
<h3 id="expression-language"><a class="header" href="#expression-language">Expression Language</a></h3>
|
||
<p>Conditions and queries use:</p>
|
||
<ul>
|
||
<li><strong>Comparisons</strong>: <code>age > 18</code>, <code>energy <= 0.5</code></li>
|
||
<li><strong>Equality</strong>: <code>status is active</code>, <code>ready is true</code></li>
|
||
<li><strong>Logic</strong>: <code>tired and hungry</code>, <code>rich or lucky</code>, <code>not ready</code></li>
|
||
<li><strong>Field access</strong>: <code>self.health</code>, <code>other.bond</code></li>
|
||
<li><strong>Quantifiers</strong>: <code>forall x in children: x.happy</code></li>
|
||
</ul>
|
||
<h2 id="compilation-model"><a class="header" href="#compilation-model">Compilation Model</a></h2>
|
||
<h3 id="source--ast--sbir--runtime"><a class="header" href="#source--ast--sbir--runtime">Source → AST → SBIR → Runtime</a></h3>
|
||
<pre><code>.sb files → Parser → Abstract Syntax Tree → Resolver → SBIR Binary
|
||
</code></pre>
|
||
<p><strong>SBIR</strong> (Storybook Intermediate Representation) is a compact binary format that:</p>
|
||
<ul>
|
||
<li>Resolves all cross-file references</li>
|
||
<li>Validates semantic constraints</li>
|
||
<li>Optimizes for simulation runtime</li>
|
||
</ul>
|
||
<h3 id="validation-layers"><a class="header" href="#validation-layers">Validation Layers</a></h3>
|
||
<ol>
|
||
<li><strong>Lexical</strong>: Valid tokens and syntax</li>
|
||
<li><strong>Syntactic</strong>: Correct grammar structure</li>
|
||
<li><strong>Semantic</strong>: Type checking, reference resolution</li>
|
||
<li><strong>Domain</strong>: Narrative constraints (bond ranges, schedule overlaps)</li>
|
||
</ol>
|
||
<h2 id="file-organization"><a class="header" href="#file-organization">File Organization</a></h2>
|
||
<h3 id="project-structure-1"><a class="header" href="#project-structure-1">Project Structure</a></h3>
|
||
<pre><code>my-storybook/
|
||
├── characters/
|
||
│ ├── baker.sb
|
||
│ └── family.sb
|
||
├── behaviors/
|
||
│ └── daily_routine.sb
|
||
├── world/
|
||
│ ├── locations.sb
|
||
│ └── institutions.sb
|
||
└── schema/
|
||
├── species.sb
|
||
└── templates.sb
|
||
</code></pre>
|
||
<h3 id="import-system"><a class="header" href="#import-system">Import System</a></h3>
|
||
<p>Use <code>use</code> statements to reference definitions from other files:</p>
|
||
<pre><code class="language-storybook">use schema::species::Human;
|
||
use schema::templates::Adult;
|
||
|
||
character Baker: Human from Adult {
|
||
// ...
|
||
}
|
||
</code></pre>
|
||
<p><strong>Resolution order:</strong></p>
|
||
<ol>
|
||
<li>Same file</li>
|
||
<li>Explicitly imported</li>
|
||
<li>Error if not found</li>
|
||
</ol>
|
||
<h2 id="quick-reference"><a class="header" href="#quick-reference">Quick Reference</a></h2>
|
||
<h3 id="character-declaration"><a class="header" href="#character-declaration">Character Declaration</a></h3>
|
||
<pre><code class="language-storybook">character Name: Species from Template {
|
||
field: value
|
||
field: value
|
||
---prose_tag
|
||
Text content
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="behavior-tree"><a class="header" href="#behavior-tree">Behavior Tree</a></h3>
|
||
<pre><code class="language-storybook">behavior Name {
|
||
choose label { // Selector
|
||
then label { ... } // Sequence
|
||
if (condition) // Condition
|
||
ActionName // Action
|
||
include path // Subtree
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="life-arc"><a class="header" href="#life-arc">Life Arc</a></h3>
|
||
<pre><code class="language-storybook">life_arc Name {
|
||
state StateName {
|
||
on condition -> NextState
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="schedule"><a class="header" href="#schedule">Schedule</a></h3>
|
||
<pre><code class="language-storybook">schedule Name {
|
||
08:00 -> 12:00: activity { }
|
||
12:00 -> 13:00: lunch { }
|
||
}
|
||
</code></pre>
|
||
<h3 id="relationship"><a class="header" href="#relationship">Relationship</a></h3>
|
||
<pre><code class="language-storybook">relationship Name {
|
||
Person1 as role
|
||
Person2 as role
|
||
bond: 0.85
|
||
}
|
||
</code></pre>
|
||
<h2 id="next-steps-8"><a class="header" href="#next-steps-8">Next Steps</a></h2>
|
||
<p>Dive deeper into each declaration type:</p>
|
||
<ul>
|
||
<li><a href="reference/10-characters.html">Characters</a> - Detailed character syntax</li>
|
||
<li><a href="reference/11-behavior-trees.html">Behavior Trees</a> - Complete behavior node reference</li>
|
||
<li><a href="reference/12-decorators.html">Decorators</a> - All decorator types</li>
|
||
<li><a href="reference/13-life-arcs.html">Life Arcs</a> - State machine semantics</li>
|
||
<li><a href="reference/14-schedules.html">Schedules</a> - Time-based planning</li>
|
||
<li><a href="reference/15-relationships.html">Relationships</a> - Connection modeling</li>
|
||
<li><a href="reference/16-other-declarations.html">Other Declarations</a> - Templates, institutions, etc.</li>
|
||
<li><a href="reference/17-expressions.html">Expressions</a> - Full expression language</li>
|
||
<li><a href="reference/18-value-types.html">Value Types</a> - All field value types</li>
|
||
<li><a href="reference/19-validation.html">Validation</a> - Error checking rules</li>
|
||
</ul>
|
||
<hr />
|
||
<p><strong>Philosophy Note</strong>: Storybook treats narrative as data. Characters aren’t objects with methods - they’re <strong>declarations</strong> of traits, connected by <strong>behaviors</strong> and <strong>relationships</strong>. This separation enables rich analysis, modification, and simulation of narrative worlds.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="characters"><a class="header" href="#characters">Characters</a></h1>
|
||
<p>Characters are the primary entities in Storybook—the people, creatures, and beings that inhabit your world. Each character has a set of attributes that define who they are, what they can do, and how they relate to the world around them.</p>
|
||
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><character-decl> ::= "character" <identifier> <species-clause>? <template-clause>? <body>
|
||
|
||
<species-clause> ::= ":" <qualified-path>
|
||
|
||
<template-clause> ::= "from" <qualified-path> ("," <qualified-path>)*
|
||
|
||
<body> ::= "{" <body-item>* "}"
|
||
|
||
<body-item> ::= <field>
|
||
| <uses-behaviors>
|
||
| <uses-schedule>
|
||
|
||
<uses-behaviors> ::= "uses" "behaviors" ":" <behavior-link-list>
|
||
|
||
<uses-schedule> ::= "uses" ("schedule" | "schedules") ":" (<qualified-path> | <schedule-list>)
|
||
|
||
<behavior-link-list> ::= "[" <behavior-link> ("," <behavior-link>)* "]"
|
||
|
||
<behavior-link> ::= "{" <behavior-link-field>* "}"
|
||
|
||
<behavior-link-field> ::= "tree" ":" <qualified-path>
|
||
| "when" ":" <expression>
|
||
| "priority" ":" ("low" | "normal" | "high" | "critical")
|
||
|
||
<schedule-list> ::= "[" <qualified-path> ("," <qualified-path>)* "]"
|
||
|
||
<field> ::= <identifier> ":" <value>
|
||
|
||
<value> ::= <literal>
|
||
| <qualified-path>
|
||
| <list>
|
||
| <object>
|
||
| <prose-block>
|
||
| <override>
|
||
|
||
<prose-block> ::= "---" <identifier> <content> "---"
|
||
</code></pre>
|
||
<h2 id="components"><a class="header" href="#components">Components</a></h2>
|
||
<h3 id="name"><a class="header" href="#name">Name</a></h3>
|
||
<p>The character’s identifier. Must be unique within its scope and follow standard identifier rules (alphanumeric + underscore, cannot start with digit).</p>
|
||
<h3 id="species-optional"><a class="header" href="#species-optional">Species (Optional)</a></h3>
|
||
<p>The species clause (<code>: SpeciesName</code>) defines what the character fundamentally <em>is</em>. This is distinct from templates, which define what attributes they <em>have</em>.</p>
|
||
<ul>
|
||
<li><strong>Purpose</strong>: Ontological typing—what kind of being this is</li>
|
||
<li><strong>Validation</strong>: Must reference a defined <code>species</code> declaration</li>
|
||
<li><strong>Single inheritance</strong>: A character can only have one species</li>
|
||
<li><strong>Default behavior</strong>: Species fields are inherited automatically</li>
|
||
</ul>
|
||
<p>Example:</p>
|
||
<pre><code class="language-storybook">character Martha: Human {
|
||
age: 34
|
||
}
|
||
</code></pre>
|
||
<h3 id="template-inheritance-optional"><a class="header" href="#template-inheritance-optional">Template Inheritance (Optional)</a></h3>
|
||
<p>The template clause (<code>from Template1, Template2</code>) specifies templates from which the character inherits fields. Templates provide reusable attribute sets.</p>
|
||
<ul>
|
||
<li><strong>Purpose</strong>: Compositional inheritance—mix-and-match capabilities and traits</li>
|
||
<li><strong>Multiple inheritance</strong>: Characters can inherit from multiple templates</li>
|
||
<li><strong>Merge semantics</strong>: Fields from later templates override earlier ones</li>
|
||
<li><strong>Override allowed</strong>: Character fields override all inherited fields</li>
|
||
</ul>
|
||
<p>Example:</p>
|
||
<pre><code class="language-storybook">character Martha: Human from Baker, BusinessOwner {
|
||
specialty: "sourdough"
|
||
}
|
||
</code></pre>
|
||
<h3 id="fields"><a class="header" href="#fields">Fields</a></h3>
|
||
<p>Fields define the character’s attributes using the standard field syntax. All <a href="reference/./18-value-types.html">value types</a> are supported.</p>
|
||
<p><strong>Common field categories:</strong></p>
|
||
<ul>
|
||
<li><strong>Physical traits</strong>: <code>height</code>, <code>weight</code>, <code>age</code>, <code>eye_color</code></li>
|
||
<li><strong>Personality</strong>: <code>confidence</code>, <code>patience</code>, <code>dedication</code></li>
|
||
<li><strong>Professional</strong>: <code>skill_level</code>, <code>specialty</code>, <code>recipes_mastered</code></li>
|
||
<li><strong>State tracking</strong>: <code>energy</code>, <code>mood</code>, <code>orders_today</code></li>
|
||
<li><strong>Capabilities</strong>: <code>can_teach</code>, <code>can_work_independently</code></li>
|
||
</ul>
|
||
<h3 id="prose-blocks-2"><a class="header" href="#prose-blocks-2">Prose Blocks</a></h3>
|
||
<p>Characters can contain multiple prose blocks for narrative content. Common tags:</p>
|
||
<ul>
|
||
<li><code>---backstory</code>: Character history and origin</li>
|
||
<li><code>---appearance</code>: Physical description</li>
|
||
<li><code>---personality</code>: Behavioral traits and quirks</li>
|
||
<li><code>---motivation</code>: Goals and desires</li>
|
||
<li><code>---secrets</code>: Hidden information</li>
|
||
</ul>
|
||
<p>Prose blocks are narrative-only and do not affect simulation logic.</p>
|
||
<h3 id="behavior-integration"><a class="header" href="#behavior-integration">Behavior Integration</a></h3>
|
||
<p>Characters can link to <a href="reference/./14-behavior-trees.html">behavior trees</a> using the <code>uses behaviors</code> clause.</p>
|
||
<pre><code class="language-storybook">character Guard {
|
||
uses behaviors: [
|
||
{
|
||
tree: combat::patrol_route
|
||
priority: normal
|
||
},
|
||
{
|
||
tree: combat::engage_intruder
|
||
when: threat_detected
|
||
priority: high
|
||
}
|
||
]
|
||
}
|
||
</code></pre>
|
||
<p>Each behavior link includes:</p>
|
||
<ul>
|
||
<li><strong><code>tree</code></strong>: Qualified path to the behavior tree (required)</li>
|
||
<li><strong><code>when</code></strong>: Condition expression for activation (optional)</li>
|
||
<li><strong><code>priority</code></strong>: Execution priority (optional, default: <code>normal</code>)</li>
|
||
</ul>
|
||
<p>See <a href="reference/./11-behavior-trees.html">Behavior Trees</a> for details on behavior tree syntax and semantics.</p>
|
||
<h3 id="schedule-integration"><a class="header" href="#schedule-integration">Schedule Integration</a></h3>
|
||
<p>Characters can follow <a href="reference/./16-schedules.html">schedules</a> using the <code>uses schedule</code> or <code>uses schedules</code> clause.</p>
|
||
<pre><code class="language-storybook">character Baker {
|
||
uses schedule: BakerySchedule
|
||
}
|
||
|
||
character Innkeeper {
|
||
uses schedules: [WeekdaySchedule, WeekendSchedule]
|
||
}
|
||
</code></pre>
|
||
<ul>
|
||
<li>Single schedule: <code>uses schedule: ScheduleName</code></li>
|
||
<li>Multiple schedules: <code>uses schedules: [Schedule1, Schedule2]</code></li>
|
||
</ul>
|
||
<p>The runtime selects the appropriate schedule based on temporal constraints. See <a href="reference/./14-schedules.html">Schedules</a> for composition and selection semantics.</p>
|
||
<h2 id="species-vs-templates-1"><a class="header" href="#species-vs-templates-1">Species vs. Templates</a></h2>
|
||
<p>The distinction between species (<code>:</code>) and templates (<code>from</code>) reflects ontological vs. compositional typing:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Species (<code>:</code>)</th><th>Templates (<code>from</code>)</th></tr></thead><tbody>
|
||
<tr><td><strong>Semantics</strong></td><td>What the character <em>is</em></td><td>What the character <em>has</em></td></tr>
|
||
<tr><td><strong>Cardinality</strong></td><td>Exactly one</td><td>Zero or more</td></tr>
|
||
<tr><td><strong>Example</strong></td><td><code>: Human</code>, <code>: Dragon</code></td><td><code>from Warrior, Mage</code></td></tr>
|
||
<tr><td><strong>Purpose</strong></td><td>Fundamental nature</td><td>Reusable trait sets</td></tr>
|
||
<tr><td><strong>Override</strong></td><td>Can override species fields</td><td>Can override template fields</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>Example showing both:</p>
|
||
<pre><code class="language-storybook">species Dragon {
|
||
max_lifespan: 1000
|
||
can_fly: true
|
||
}
|
||
|
||
template Hoarder {
|
||
treasure_value: 0..1000000
|
||
greed_level: 0.0..1.0
|
||
}
|
||
|
||
template Ancient {
|
||
age: 500..1000
|
||
wisdom: 0.8..1.0
|
||
}
|
||
|
||
character Smaug: Dragon from Hoarder, Ancient {
|
||
age: 850
|
||
treasure_value: 500000
|
||
greed_level: 0.95
|
||
}
|
||
</code></pre>
|
||
<h2 id="field-resolution-order"><a class="header" href="#field-resolution-order">Field Resolution Order</a></h2>
|
||
<p>When a character inherits from species and templates, fields are resolved in this order (later overrides earlier):</p>
|
||
<ol>
|
||
<li><strong>Species fields</strong> (base ontology)</li>
|
||
<li><strong>Template fields</strong> (left to right in <code>from</code> clause)</li>
|
||
<li><strong>Character fields</strong> (highest priority)</li>
|
||
</ol>
|
||
<p>Example:</p>
|
||
<pre><code class="language-storybook">species Human {
|
||
lifespan: 80
|
||
speed: 1.0
|
||
}
|
||
|
||
template Warrior {
|
||
speed: 1.5
|
||
strength: 10
|
||
}
|
||
|
||
template Berserker {
|
||
speed: 2.0
|
||
strength: 15
|
||
}
|
||
|
||
character Conan: Human from Warrior, Berserker {
|
||
strength: 20
|
||
}
|
||
|
||
// Resolved fields:
|
||
// lifespan: 80 (from Human)
|
||
// speed: 2.0 (Berserker overrides Warrior overrides Human)
|
||
// strength: 20 (character overrides Berserker)
|
||
</code></pre>
|
||
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
|
||
<p>The Storybook compiler enforces these validation rules:</p>
|
||
<ol>
|
||
<li><strong>Unique names</strong>: Character names must be unique within their module</li>
|
||
<li><strong>Species exists</strong>: If specified, the species must reference a defined <code>species</code> declaration</li>
|
||
<li><strong>Templates exist</strong>: All templates in the <code>from</code> clause must reference defined <code>template</code> declarations</li>
|
||
<li><strong>No circular inheritance</strong>: Templates cannot form circular dependency chains</li>
|
||
<li><strong>Field type consistency</strong>: Field values must match expected types from species/templates</li>
|
||
<li><strong>Reserved fields</strong>: Cannot use reserved keywords as field names</li>
|
||
<li><strong>Behavior trees exist</strong>: All behavior tree references must resolve to defined <code>behavior</code> declarations</li>
|
||
<li><strong>Schedules exist</strong>: All schedule references must resolve to defined <code>schedule</code> declarations</li>
|
||
<li><strong>Prose tag uniqueness</strong>: Each prose tag can appear at most once per character</li>
|
||
</ol>
|
||
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
|
||
<h3 id="basic-character"><a class="header" href="#basic-character">Basic Character</a></h3>
|
||
<pre><code class="language-storybook">character SimpleMerchant {
|
||
name: "Gregor"
|
||
occupation: "Fish Merchant"
|
||
wealth: 50
|
||
|
||
---personality
|
||
A straightforward fish seller at the market. Honest, hardworking,
|
||
and always smells faintly of mackerel.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="character-with-species"><a class="header" href="#character-with-species">Character with Species</a></h3>
|
||
<pre><code class="language-storybook">character Martha: Human {
|
||
age: 34
|
||
skill_level: 0.95
|
||
specialty: "sourdough"
|
||
|
||
---backstory
|
||
Martha learned to bake from her grandmother, starting at age
|
||
twelve. She now runs the most popular bakery in town.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="character-with-template-inheritance"><a class="header" href="#character-with-template-inheritance">Character with Template Inheritance</a></h3>
|
||
<pre><code class="language-storybook">character Jane: Human from Baker, PastrySpecialist {
|
||
age: 36
|
||
specialty: "pastries"
|
||
recipes_mastered: 120
|
||
|
||
years_experience: 18
|
||
can_teach: true
|
||
|
||
---appearance
|
||
A focused woman with flour-dusted apron and steady hands.
|
||
Known for her intricate pastry decorations and precise
|
||
temperature control.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="character-with-all-features"><a class="header" href="#character-with-all-features">Character with All Features</a></h3>
|
||
<pre><code class="language-storybook">character CityGuard: Human from CombatTraining, LawEnforcement {
|
||
age: 30
|
||
rank: "Sergeant"
|
||
|
||
// Physical traits
|
||
height: 175
|
||
strength: 12
|
||
|
||
// Equipment
|
||
has_weapon: true
|
||
armor_level: 2
|
||
|
||
// Behavior integration
|
||
uses behaviors: [
|
||
{
|
||
tree: guards::patrol_route
|
||
priority: normal
|
||
},
|
||
{
|
||
tree: guards::engage_hostile
|
||
when: threat_detected
|
||
priority: high
|
||
},
|
||
{
|
||
tree: guards::sound_alarm
|
||
when: emergency
|
||
priority: critical
|
||
}
|
||
]
|
||
|
||
// Schedule integration
|
||
uses schedules: [guards::day_shift, guards::night_shift]
|
||
|
||
---backstory
|
||
A veteran of the city watch, now responsible for training new recruits
|
||
while maintaining order in the merchant district.
|
||
---
|
||
|
||
---personality
|
||
Gruff exterior with a hidden soft spot for street children. Follows
|
||
the rules but knows when to look the other way.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="character-with-overrides"><a class="header" href="#character-with-overrides">Character with Overrides</a></h3>
|
||
<pre><code class="language-storybook">template WeaponUser {
|
||
damage: 5..15
|
||
accuracy: 0.7
|
||
}
|
||
|
||
character MasterSwordsman: Human from WeaponUser {
|
||
// Override template range with specific value
|
||
damage: 15
|
||
accuracy: 0.95
|
||
|
||
// Add character-specific fields
|
||
signature_move: "Whirlwind Strike"
|
||
}
|
||
</code></pre>
|
||
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
|
||
<h3 id="protagonist-definition"><a class="header" href="#protagonist-definition">Protagonist Definition</a></h3>
|
||
<p>Define rich, dynamic protagonists with complex attributes:</p>
|
||
<pre><code class="language-storybook">character Elena: Human from Scholar, Diplomat {
|
||
age: 28
|
||
intelligence: 18
|
||
charisma: 16
|
||
|
||
languages_known: ["Common", "Elvish", "Draconic"]
|
||
books_read: 347
|
||
|
||
current_quest: "Broker peace between warring kingdoms"
|
||
|
||
---backstory
|
||
Raised in the Grand Library, Elena discovered ancient texts that
|
||
hinted at a forgotten alliance between humans and dragons. She now
|
||
seeks to revive that alliance to end the current war.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="npc-templates"><a class="header" href="#npc-templates">NPC Templates</a></h3>
|
||
<p>Create diverse NPCs from templates:</p>
|
||
<pre><code class="language-storybook">template Villager {
|
||
occupation: "Farmer"
|
||
wealth: 10..50
|
||
disposition: 0.0..1.0 // 0=hostile, 1=friendly
|
||
}
|
||
|
||
character Oswald: Human from Villager {
|
||
occupation: "Blacksmith"
|
||
wealth: 45
|
||
disposition: 0.8
|
||
}
|
||
|
||
character Mildred: Human from Villager {
|
||
occupation: "Baker"
|
||
wealth: 35
|
||
disposition: 0.95
|
||
}
|
||
</code></pre>
|
||
<h3 id="ensemble-casts"><a class="header" href="#ensemble-casts">Ensemble Casts</a></h3>
|
||
<p>Define multiple related characters:</p>
|
||
<pre><code class="language-storybook">template BakeryStaff {
|
||
punctuality: 0.5..1.0
|
||
teamwork: 0.5..1.0
|
||
}
|
||
|
||
template Apprentice {
|
||
skill_level: 0.0..0.5
|
||
dedication: 0.5..1.0
|
||
}
|
||
|
||
character Elena: Human from BakeryStaff, Apprentice {
|
||
age: 16
|
||
natural_talent: 0.8
|
||
dedication: 0.9
|
||
|
||
---backstory
|
||
Elena comes from a family of farmers who could never afford to
|
||
buy bread from the bakery. When Martha offered her an apprenticeship,
|
||
she jumped at the chance to learn a trade.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./16-other-declarations.html#species">Species</a> - Species declarations</li>
|
||
<li><a href="reference/./16-other-declarations.html#templates">Templates</a> - Template definitions and strict mode</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - All supported value types</li>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> - Character behavior integration</li>
|
||
<li><a href="reference/./14-schedules.html">Schedules</a> - Character schedule integration</li>
|
||
<li><a href="reference/./15-relationships.html">Relationships</a> - Relationships between characters</li>
|
||
<li><a href="reference/./13-life-arcs.html">Life Arcs</a> - Character state machines over time</li>
|
||
</ul>
|
||
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Instantiation</strong>: Characters are concrete instances; they cannot be instantiated further</li>
|
||
<li><strong>Composition</strong>: Prefer template composition over deep species hierarchies</li>
|
||
<li><strong>Modularity</strong>: Characters can reference behaviors and schedules from other modules</li>
|
||
<li><strong>Narrative-driven</strong>: Use prose blocks to embed storytelling directly with data</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="behavior-trees"><a class="header" href="#behavior-trees">Behavior Trees</a></h1>
|
||
<p>Behavior trees define the decision-making logic for characters, institutions, and other entities. They model how an entity chooses actions, responds to conditions, and adapts to its environment. Storybook uses behavior trees for character AI, NPC routines, and complex reactive behavior.</p>
|
||
<h2 id="what-is-a-behavior-tree-1"><a class="header" href="#what-is-a-behavior-tree-1">What is a Behavior Tree?</a></h2>
|
||
<p>A behavior tree is a hierarchical structure that executes from root to leaves, making decisions based on success/failure of child nodes:</p>
|
||
<ul>
|
||
<li><strong>Composite nodes</strong> (choose, then) have multiple children and control flow</li>
|
||
<li><strong>Condition nodes</strong> (if, when) test predicates</li>
|
||
<li><strong>Action nodes</strong> execute concrete behaviors</li>
|
||
<li><strong>Decorator nodes</strong> (repeat, invert, timeout, etc.) modify child behavior</li>
|
||
<li><strong>Subtree nodes</strong> (include) reference other behavior trees</li>
|
||
</ul>
|
||
<p>Behavior trees are evaluated every tick (frame), flowing through the tree from root to leaves until a node returns success or failure.</p>
|
||
<h2 id="syntax-1"><a class="header" href="#syntax-1">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><behavior-decl> ::= "behavior" <identifier> <body>
|
||
|
||
<body> ::= "{" <prose-blocks>* <behavior-node> "}"
|
||
|
||
<behavior-node> ::= <selector>
|
||
| <sequence>
|
||
| <condition>
|
||
| <action>
|
||
| <decorator>
|
||
| <subtree>
|
||
|
||
<selector> ::= "choose" <label>? "{" <behavior-node>+ "}"
|
||
|
||
<sequence> ::= "then" <label>? "{" <behavior-node>+ "}"
|
||
|
||
<condition> ::= ("if" | "when") "(" <expression> ")"
|
||
|
||
<action> ::= <identifier> ( "(" <param-list> ")" )?
|
||
|
||
<param-list> ::= <field> ("," <field>)*
|
||
|
||
<decorator> ::= <decorator-type> ("(" <params> ")")? "{" <behavior-node> "}"
|
||
|
||
<subtree> ::= "include" <qualified-path>
|
||
|
||
<label> ::= <identifier>
|
||
</code></pre>
|
||
<h2 id="composite-nodes"><a class="header" href="#composite-nodes">Composite Nodes</a></h2>
|
||
<h3 id="selector-choose"><a class="header" href="#selector-choose">Selector: <code>choose</code></a></h3>
|
||
<p>A selector tries its children in order until one succeeds. Returns success if any child succeeds, failure if all fail.</p>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Try first child</li>
|
||
<li>If succeeds -> return success</li>
|
||
<li>If fails -> try next child</li>
|
||
<li>If all fail -> return failure</li>
|
||
</ol>
|
||
<p><strong>Use case:</strong> “Try A, if that fails try B, if that fails try C…”</p>
|
||
<pre><code class="language-storybook">behavior GuardBehavior {
|
||
choose guard_actions {
|
||
AttackIntruder // Try first
|
||
SoundAlarm // If attack fails, sound alarm
|
||
FleeInPanic // If alarm fails, flee
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Named selectors:</strong></p>
|
||
<pre><code class="language-storybook">choose service_options {
|
||
then serve_regular {
|
||
CustomerIsWaiting
|
||
TakeOrder
|
||
}
|
||
|
||
then restock_display {
|
||
DisplayIsLow
|
||
FetchFromKitchen
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>Labels are optional but recommended for complex trees–they improve readability and debugging.</p>
|
||
<h3 id="sequence-then"><a class="header" href="#sequence-then">Sequence: <code>then</code></a></h3>
|
||
<p>A sequence runs its children in order until one fails. Returns success only if all children succeed, failure if any fails.</p>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Run first child</li>
|
||
<li>If fails -> return failure</li>
|
||
<li>If succeeds -> run next child</li>
|
||
<li>If all succeed -> return success</li>
|
||
</ol>
|
||
<p><strong>Use case:</strong> “Do A, then B, then C… all must succeed”</p>
|
||
<pre><code class="language-storybook">behavior BrewingSequence {
|
||
then brew_potion {
|
||
GatherIngredients // Must succeed
|
||
MixIngredients // Then this must succeed
|
||
Boil // Then this must succeed
|
||
BottlePotion // Finally this
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Named sequences:</strong></p>
|
||
<pre><code class="language-storybook">then prepare_sourdough {
|
||
MixDough
|
||
KneadDough
|
||
ShapeLoaves
|
||
}
|
||
</code></pre>
|
||
<h3 id="nesting-composites"><a class="header" href="#nesting-composites">Nesting Composites</a></h3>
|
||
<p>Composite nodes can nest arbitrarily deep:</p>
|
||
<pre><code class="language-storybook">behavior ComplexAI {
|
||
choose root_decision {
|
||
// Combat branch
|
||
then engage_combat {
|
||
if(enemy_nearby)
|
||
choose combat_tactics {
|
||
AttackWithSword
|
||
AttackWithMagic
|
||
DefendAndWait
|
||
}
|
||
}
|
||
|
||
// Exploration branch
|
||
then explore_area {
|
||
if(safe)
|
||
choose exploration_mode {
|
||
SearchForTreasure
|
||
MapTerritory
|
||
Rest
|
||
}
|
||
}
|
||
|
||
// Default: Idle
|
||
Idle
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="condition-nodes"><a class="header" href="#condition-nodes">Condition Nodes</a></h2>
|
||
<p>Conditions test expressions and return success/failure based on the result.</p>
|
||
<h3 id="if-vs-when"><a class="header" href="#if-vs-when"><code>if</code> vs. <code>when</code></a></h3>
|
||
<p>Both are semantically identical–use whichever reads better in context:</p>
|
||
<pre><code class="language-storybook">behavior Example {
|
||
choose options {
|
||
// "if" for state checks
|
||
then branch_a {
|
||
if(player_nearby)
|
||
Attack
|
||
}
|
||
|
||
// "when" for event-like conditions
|
||
then branch_b {
|
||
when(alarm_triggered)
|
||
Flee
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="condition-syntax"><a class="header" href="#condition-syntax">Condition Syntax</a></h3>
|
||
<pre><code class="language-storybook">if(health < 20) // Comparison
|
||
when(is_hostile) // Boolean field
|
||
if(distance > 10 and has_weapon) // Logical AND
|
||
when(not is_stunned or is_enraged) // Logical OR with NOT
|
||
</code></pre>
|
||
<p>See <a href="reference/./17-expressions.html">Expression Language</a> for complete expression syntax.</p>
|
||
<h2 id="action-nodes"><a class="header" href="#action-nodes">Action Nodes</a></h2>
|
||
<p>Actions are leaf nodes that execute concrete behaviors. They reference action implementations in the runtime.</p>
|
||
<h3 id="basic-actions"><a class="header" href="#basic-actions">Basic Actions</a></h3>
|
||
<pre><code class="language-storybook">behavior SimpleActions {
|
||
then do_things {
|
||
MoveForward
|
||
TurnLeft
|
||
Attack
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="actions-with-parameters"><a class="header" href="#actions-with-parameters">Actions with Parameters</a></h3>
|
||
<p>Actions can have named parameters using parenthesis syntax:</p>
|
||
<pre><code class="language-storybook">behavior ParameterizedActions {
|
||
then patrol {
|
||
MoveTo(destination: "Waypoint1", speed: 1.5)
|
||
WaitFor(duration: 5s)
|
||
MoveTo(destination: "Waypoint2", speed: 1.5)
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Parameter passing:</strong></p>
|
||
<ul>
|
||
<li>Parameters are passed as fields (name: value)</li>
|
||
<li>All <a href="reference/./18-value-types.html">value types</a> supported</li>
|
||
<li>Runtime validates parameter types</li>
|
||
</ul>
|
||
<pre><code class="language-storybook">behavior RichParameters {
|
||
then complex_action {
|
||
CastSpell(spell: "Fireball", target: enemy_position, power: 75, multicast: false)
|
||
Heal(amount: 50, target: self)
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="decorator-nodes"><a class="header" href="#decorator-nodes">Decorator Nodes</a></h2>
|
||
<p>Decorators wrap a single child node and modify its behavior. See <a href="reference/./12-decorators.html">Decorators</a> for complete reference.</p>
|
||
<h3 id="common-decorators"><a class="header" href="#common-decorators">Common Decorators</a></h3>
|
||
<pre><code class="language-storybook">behavior DecoratorExamples {
|
||
choose {
|
||
// Repeat infinitely
|
||
repeat {
|
||
PatrolRoute
|
||
}
|
||
|
||
// Repeat exactly 3 times
|
||
repeat(3) {
|
||
CheckDoor
|
||
}
|
||
|
||
// Repeat 2 to 5 times
|
||
repeat(2..5) {
|
||
SearchArea
|
||
}
|
||
|
||
// Invert success/failure
|
||
invert {
|
||
IsEnemyNearby // Returns success if enemy NOT nearby
|
||
}
|
||
|
||
// Retry up to 5 times on failure
|
||
retry(5) {
|
||
OpenLockedDoor
|
||
}
|
||
|
||
// Timeout after 10 seconds
|
||
timeout(10s) {
|
||
SolveComplexPuzzle
|
||
}
|
||
|
||
// Cooldown: only run once per 30 seconds
|
||
cooldown(30s) {
|
||
FireCannon
|
||
}
|
||
|
||
// If: only run if condition true
|
||
if(health > 50) {
|
||
AggressiveAttack
|
||
}
|
||
|
||
// Always succeed regardless of child result
|
||
succeed_always {
|
||
AttemptOptionalTask
|
||
}
|
||
|
||
// Always fail regardless of child result
|
||
fail_always {
|
||
ImpossipleTask
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="subtree-references-1"><a class="header" href="#subtree-references-1">Subtree References</a></h2>
|
||
<p>The <code>include</code> keyword references another behavior tree, enabling modularity and reuse.</p>
|
||
<h3 id="basic-subtree"><a class="header" href="#basic-subtree">Basic Subtree</a></h3>
|
||
<pre><code class="language-storybook">behavior PatrolRoute {
|
||
then patrol {
|
||
MoveTo(destination: "Waypoint1")
|
||
MoveTo(destination: "Waypoint2")
|
||
MoveTo(destination: "Waypoint3")
|
||
}
|
||
}
|
||
|
||
behavior GuardBehavior {
|
||
choose guard_ai {
|
||
then combat {
|
||
if(enemy_nearby)
|
||
include combat::engage
|
||
}
|
||
|
||
include PatrolRoute // Reuse patrol behavior
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="qualified-subtree-references"><a class="header" href="#qualified-subtree-references">Qualified Subtree References</a></h3>
|
||
<pre><code class="language-storybook">behavior ComplexAI {
|
||
choose ai_modes {
|
||
include behaviors::combat::melee
|
||
include behaviors::combat::ranged
|
||
include behaviors::exploration::search
|
||
include behaviors::social::greet
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="named-nodes"><a class="header" href="#named-nodes">Named Nodes</a></h2>
|
||
<p>Both composite nodes (<code>choose</code>, <code>then</code>) can have optional labels:</p>
|
||
<pre><code class="language-storybook">behavior NamedNodeExample {
|
||
choose daily_priority { // Named selector
|
||
then handle_special_orders { // Named sequence
|
||
CheckOrderQueue
|
||
PrepareSpecialIngredients
|
||
BakeSpecialItem
|
||
}
|
||
|
||
choose regular_work { // Named selector
|
||
then morning_bread { // Named sequence
|
||
MixSourdough
|
||
BakeLoaves
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Benefits:</strong></p>
|
||
<ul>
|
||
<li><strong>Readability</strong>: Tree structure is self-documenting</li>
|
||
<li><strong>Debugging</strong>: Named nodes appear in execution traces</li>
|
||
<li><strong>Narrative</strong>: Labels can be narrative (“handle_special_orders” vs. anonymous node)</li>
|
||
</ul>
|
||
<p><strong>Guidelines:</strong></p>
|
||
<ul>
|
||
<li>Use labels for important structural nodes</li>
|
||
<li>Use descriptive, narrative names</li>
|
||
<li>Omit labels for trivial nodes</li>
|
||
</ul>
|
||
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
|
||
<h3 id="simple-guard-ai"><a class="header" href="#simple-guard-ai">Simple Guard AI</a></h3>
|
||
<pre><code class="language-storybook">behavior GuardPatrol {
|
||
choose guard_logic {
|
||
// Combat if enemy nearby
|
||
then combat_mode {
|
||
if(enemy_nearby and has_weapon)
|
||
Attack
|
||
}
|
||
|
||
// Sound alarm if see intruder
|
||
then alarm_mode {
|
||
if(intruder_detected)
|
||
SoundAlarm
|
||
}
|
||
|
||
// Default: Patrol
|
||
then patrol_mode {
|
||
include PatrolRoute
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="complex-npc-behavior"><a class="header" href="#complex-npc-behavior">Complex NPC Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Innkeeper_DailyRoutine {
|
||
---description
|
||
The innkeeper's behavior throughout the day. Uses selectors for
|
||
decision-making and sequences for multi-step actions.
|
||
---
|
||
|
||
choose daily_activity {
|
||
// Morning: Prepare for opening
|
||
then morning_prep {
|
||
when(time_is_morning)
|
||
then prep_sequence {
|
||
UnlockDoor
|
||
LightFireplace
|
||
PrepareBreakfast
|
||
WaitForGuests
|
||
}
|
||
}
|
||
|
||
// Day: Serve customers
|
||
then day_service {
|
||
when(time_is_daytime)
|
||
choose service_mode {
|
||
// Serve customer if present
|
||
then serve {
|
||
if(customer_waiting)
|
||
GreetCustomer
|
||
TakeOrder
|
||
ServeMeal
|
||
CollectPayment
|
||
}
|
||
|
||
// Restock if needed
|
||
then restock {
|
||
if(inventory_low)
|
||
ReplenishInventory
|
||
}
|
||
|
||
// Default: Clean
|
||
CleanTables
|
||
}
|
||
}
|
||
|
||
// Night: Close up
|
||
then evening_close {
|
||
when(time_is_evening)
|
||
then close_sequence {
|
||
DismissRemainingGuests
|
||
LockDoor
|
||
CountMoney
|
||
GoToBed
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="morning-baking-routine"><a class="header" href="#morning-baking-routine">Morning Baking Routine</a></h3>
|
||
<pre><code class="language-storybook">behavior Baker_MorningRoutine {
|
||
---description
|
||
Martha's morning routine: prepare dough step by step,
|
||
from mixing to shaping to baking.
|
||
---
|
||
|
||
then morning_baking {
|
||
// Start with sourdough
|
||
then prepare_starter {
|
||
CheckStarter
|
||
FeedStarter
|
||
WaitForActivity
|
||
}
|
||
|
||
// Mix the dough
|
||
then mix_dough {
|
||
MeasureFlour
|
||
AddWater
|
||
IncorporateStarter
|
||
}
|
||
|
||
// Knead and shape
|
||
then shape_loaves {
|
||
KneadDough
|
||
FirstRise
|
||
ShapeLoaves
|
||
}
|
||
|
||
// Bake
|
||
then bake {
|
||
PreHeatOven
|
||
LoadLoaves
|
||
MonitorBaking
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="repeating-behavior"><a class="header" href="#repeating-behavior">Repeating Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Bakery_CustomerServiceLoop {
|
||
---description
|
||
The bakery's continuous customer service loop. Uses infinite
|
||
repeat decorator to serve customers throughout the day.
|
||
---
|
||
|
||
repeat {
|
||
then service_cycle {
|
||
// Check for customers
|
||
choose service_mode {
|
||
then serve_waiting {
|
||
if(customer_waiting)
|
||
GreetCustomer
|
||
TakeOrder
|
||
}
|
||
|
||
then restock_display {
|
||
if(display_low)
|
||
FetchFromKitchen
|
||
ArrangeOnShelves
|
||
}
|
||
}
|
||
|
||
// Process payment
|
||
CollectPayment
|
||
ThankCustomer
|
||
|
||
// Brief pause between customers
|
||
timeout(5s) {
|
||
CleanCounter
|
||
}
|
||
|
||
PrepareForNextCustomer
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="retry-logic-for-delicate-recipes"><a class="header" href="#retry-logic-for-delicate-recipes">Retry Logic for Delicate Recipes</a></h3>
|
||
<pre><code class="language-storybook">behavior Baker_DelicatePastry {
|
||
---description
|
||
Elena attempting a delicate pastry recipe that requires
|
||
precise technique. Uses retry decorator for persistence.
|
||
---
|
||
|
||
choose baking_strategy {
|
||
// Try when conditions are right
|
||
then attempt_pastry {
|
||
if(oven_at_temperature)
|
||
// Try up to 3 times
|
||
retry(3) {
|
||
then make_pastry {
|
||
RollDoughThin
|
||
ApplyFilling
|
||
FoldAndSeal
|
||
CheckForLeaks
|
||
}
|
||
}
|
||
}
|
||
|
||
// If oven not ready, prepare meanwhile
|
||
then prepare_meanwhile {
|
||
if(not oven_at_temperature)
|
||
then prep_sequence {
|
||
PrepareIngredients
|
||
MixFilling
|
||
ChillDough
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="integration-with-characters"><a class="header" href="#integration-with-characters">Integration with Characters</a></h2>
|
||
<p>Characters link to behaviors using the <code>uses behaviors</code> clause:</p>
|
||
<pre><code class="language-storybook">character Guard {
|
||
uses behaviors: [
|
||
{
|
||
tree: guards::patrol_route
|
||
priority: normal
|
||
},
|
||
{
|
||
tree: guards::engage_hostile
|
||
when: threat_detected
|
||
priority: high
|
||
},
|
||
{
|
||
tree: guards::sound_alarm
|
||
when: emergency
|
||
priority: critical
|
||
}
|
||
]
|
||
}
|
||
</code></pre>
|
||
<p><strong>Behavior selection:</strong></p>
|
||
<ul>
|
||
<li>Multiple behaviors evaluated by priority (critical > high > normal > low)</li>
|
||
<li>Conditions (<code>when</code> clause) gate behavior activation</li>
|
||
<li>Higher-priority behaviors preempt lower-priority ones</li>
|
||
</ul>
|
||
<p>See <a href="reference/./10-characters.html#behavior-integration">Characters</a> for complete behavior linking syntax.</p>
|
||
<h2 id="execution-semantics"><a class="header" href="#execution-semantics">Execution Semantics</a></h2>
|
||
<h3 id="tick-based-evaluation"><a class="header" href="#tick-based-evaluation">Tick-Based Evaluation</a></h3>
|
||
<p>Behavior trees execute every “tick” (typically once per frame):</p>
|
||
<ol>
|
||
<li><strong>Start at root</strong> node</li>
|
||
<li><strong>Traverse</strong> down to leaves based on composite logic</li>
|
||
<li><strong>Execute</strong> leaf nodes (conditions, actions)</li>
|
||
<li><strong>Return</strong> success/failure up the tree</li>
|
||
<li><strong>Repeat</strong> next tick</li>
|
||
</ol>
|
||
<h3 id="node-return-values"><a class="header" href="#node-return-values">Node Return Values</a></h3>
|
||
<p>Every node returns one of:</p>
|
||
<ul>
|
||
<li><strong>Success</strong>: Node completed successfully</li>
|
||
<li><strong>Failure</strong>: Node failed</li>
|
||
<li><strong>Running</strong>: Node still executing (async action)</li>
|
||
</ul>
|
||
<h3 id="stateful-vs-stateless"><a class="header" href="#stateful-vs-stateless">Stateful vs. Stateless</a></h3>
|
||
<ul>
|
||
<li><strong>Stateless nodes</strong>: Evaluate fresh every tick (conditions, simple actions)</li>
|
||
<li><strong>Stateful nodes</strong>: Maintain state across ticks (decorators, long actions)</li>
|
||
</ul>
|
||
<p>Example of stateful behavior:</p>
|
||
<pre><code class="language-storybook">behavior LongAction {
|
||
timeout(30s) { // Stateful: tracks elapsed time
|
||
ComplexCalculation // May take multiple ticks
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="validation-rules-1"><a class="header" href="#validation-rules-1">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>At least one node</strong>: Behavior body must contain at least one node</li>
|
||
<li><strong>Composite children</strong>: <code>choose</code> and <code>then</code> require at least one child</li>
|
||
<li><strong>Decorator child</strong>: Decorators require exactly one child</li>
|
||
<li><strong>Action exists</strong>: Action names must reference registered actions in runtime</li>
|
||
<li><strong>Subtree exists</strong>: <code>include</code> must reference a defined <code>behavior</code> declaration</li>
|
||
<li><strong>Expression validity</strong>: Condition expressions must be well-formed</li>
|
||
<li><strong>Duration format</strong>: Decorator durations must be valid (e.g., <code>5s</code>, <code>10m</code>)</li>
|
||
<li><strong>Unique labels</strong>: Node labels (if used) should be unique within their parent</li>
|
||
<li><strong>Parameter types</strong>: Action parameters must match expected types</li>
|
||
</ol>
|
||
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
|
||
<h3 id="1-prefer-shallow-trees"><a class="header" href="#1-prefer-shallow-trees">1. Prefer Shallow Trees</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">choose {
|
||
then { then { then { then { Action } } } } // Too deep!
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">choose root {
|
||
include combat_tree
|
||
include exploration_tree
|
||
include social_tree
|
||
}
|
||
</code></pre>
|
||
<h3 id="2-use-named-nodes-for-clarity"><a class="header" href="#2-use-named-nodes-for-clarity">2. Use Named Nodes for Clarity</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">choose {
|
||
then {
|
||
IsHungry
|
||
FindFood
|
||
Eat
|
||
}
|
||
Wander
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">choose survival {
|
||
then eat_if_hungry {
|
||
IsHungry
|
||
FindFood
|
||
Eat
|
||
}
|
||
Wander
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-subtrees-for-reusability"><a class="header" href="#3-subtrees-for-reusability">3. Subtrees for Reusability</a></h3>
|
||
<p><strong>Avoid:</strong> Duplicating logic across behaviors</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">behavior Combat_Common {
|
||
then attack_sequence {
|
||
DrawWeapon
|
||
Approach
|
||
Strike
|
||
}
|
||
}
|
||
|
||
behavior Warrior {
|
||
include Combat_Common
|
||
}
|
||
|
||
behavior Guard {
|
||
include Combat_Common
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-decorators-for-timing"><a class="header" href="#4-decorators-for-timing">4. Decorators for Timing</a></h3>
|
||
<p><strong>Avoid:</strong> Manual timing in actions</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">timeout(10s) {
|
||
ComplexTask
|
||
}
|
||
|
||
cooldown(30s) {
|
||
SpecialAbility
|
||
}
|
||
</code></pre>
|
||
<h3 id="5-guard-for-preconditions"><a class="header" href="#5-guard-for-preconditions">5. Guard for Preconditions</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">then problematic {
|
||
ExpensiveAction // Always runs even if inappropriate
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">if(can_afford_action) {
|
||
ExpensiveAction // Only runs when condition passes
|
||
}
|
||
</code></pre>
|
||
<h2 id="cross-references-1"><a class="header" href="#cross-references-1">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./12-decorators.html">Decorators</a> - Complete decorator reference</li>
|
||
<li><a href="reference/./10-characters.html">Characters</a> - Linking behaviors to characters</li>
|
||
<li><a href="reference/./17-expressions.html">Expression Language</a> - Condition expression syntax</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - Parameter value types</li>
|
||
<li><a href="reference/../advanced/20-patterns.html">Design Patterns</a> - Common behavior tree patterns</li>
|
||
</ul>
|
||
<h2 id="related-concepts-1"><a class="header" href="#related-concepts-1">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Reactive AI</strong>: Behavior trees continuously react to changing conditions</li>
|
||
<li><strong>Hierarchical decision-making</strong>: Composite nodes create decision hierarchies</li>
|
||
<li><strong>Modularity</strong>: Subtrees enable behavior reuse and composition</li>
|
||
<li><strong>Narrative-driven design</strong>: Named nodes make behavior trees readable as stories</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="decorators-1"><a class="header" href="#decorators-1">Decorators</a></h1>
|
||
<p>Decorators are special nodes that wrap a single child and modify its execution behavior. They enable timing control, retry logic, conditional execution, and result inversion without modifying the child node itself.</p>
|
||
<h2 id="what-are-decorators"><a class="header" href="#what-are-decorators">What Are Decorators?</a></h2>
|
||
<p>Decorators sit between a parent and child node, transforming the child’s behavior:</p>
|
||
<pre><code>Parent
|
||
└─ Decorator
|
||
└─ Child
|
||
</code></pre>
|
||
<p>The decorator intercepts the child’s execution, potentially:</p>
|
||
<ul>
|
||
<li>Repeating it multiple times</li>
|
||
<li>Timing it out</li>
|
||
<li>Inverting its result</li>
|
||
<li>Guarding its execution</li>
|
||
<li>Forcing a specific result</li>
|
||
</ul>
|
||
<h2 id="decorator-types"><a class="header" href="#decorator-types">Decorator Types</a></h2>
|
||
<p>Storybook provides 10 decorator types:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Decorator</th><th>Purpose</th><th>Example</th></tr></thead><tbody>
|
||
<tr><td><code>repeat</code></td><td>Loop infinitely</td><td><code>repeat { Patrol }</code></td></tr>
|
||
<tr><td><code>repeat(N)</code></td><td>Loop N times</td><td><code>repeat(3) { Check }</code></td></tr>
|
||
<tr><td><code>repeat(min..max)</code></td><td>Loop min to max times</td><td><code>repeat(2..5) { Search }</code></td></tr>
|
||
<tr><td><code>invert</code></td><td>Flip success/failure</td><td><code>invert { IsEnemy }</code></td></tr>
|
||
<tr><td><code>retry(N)</code></td><td>Retry on failure (max N times)</td><td><code>retry(5) { Open }</code></td></tr>
|
||
<tr><td><code>timeout(duration)</code></td><td>Fail after duration</td><td><code>timeout(10s) { Solve }</code></td></tr>
|
||
<tr><td><code>cooldown(duration)</code></td><td>Run at most once per duration</td><td><code>cooldown(30s) { Fire }</code></td></tr>
|
||
<tr><td><code>if(condition)</code></td><td>Only run if condition true</td><td><code>if(health > 50) { Attack }</code></td></tr>
|
||
<tr><td><code>succeed_always</code></td><td>Always return success</td><td><code>succeed_always { Try }</code></td></tr>
|
||
<tr><td><code>fail_always</code></td><td>Always return failure</td><td><code>fail_always { Test }</code></td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="syntax-2"><a class="header" href="#syntax-2">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><decorator> ::= <repeat-decorator>
|
||
| <invert-decorator>
|
||
| <retry-decorator>
|
||
| <timeout-decorator>
|
||
| <cooldown-decorator>
|
||
| <if-decorator>
|
||
| <force-result-decorator>
|
||
|
||
<repeat-decorator> ::= "repeat" <repeat-spec>? "{" <behavior-node> "}"
|
||
|
||
<repeat-spec> ::= "(" <number> ")"
|
||
| "(" <number> ".." <number> ")"
|
||
|
||
<invert-decorator> ::= "invert" "{" <behavior-node> "}"
|
||
|
||
<retry-decorator> ::= "retry" "(" <number> ")" "{" <behavior-node> "}"
|
||
|
||
<timeout-decorator> ::= "timeout" "(" <duration-literal> ")" "{" <behavior-node> "}"
|
||
|
||
<cooldown-decorator> ::= "cooldown" "(" <duration-literal> ")" "{" <behavior-node> "}"
|
||
|
||
<if-decorator> ::= "if" "(" <expression> ")" "{" <behavior-node> "}"
|
||
|
||
<force-result-decorator> ::= ("succeed_always" | "fail_always") "{" <behavior-node> "}"
|
||
|
||
<duration-literal> ::= <number> ("s" | "m" | "h" | "d")
|
||
</code></pre>
|
||
<h2 id="repeat-decorators"><a class="header" href="#repeat-decorators">Repeat Decorators</a></h2>
|
||
<h3 id="infinite-repeat-repeat"><a class="header" href="#infinite-repeat-repeat">Infinite Repeat: <code>repeat</code></a></h3>
|
||
<p>Loops the child infinitely. The child is re-executed immediately after completing (success or failure).</p>
|
||
<pre><code class="language-storybook">behavior InfinitePatrol {
|
||
repeat {
|
||
PatrolRoute
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Run child</li>
|
||
<li>Child completes (success or failure)</li>
|
||
<li>Immediately run child again</li>
|
||
<li>Go to step 2 (forever)</li>
|
||
</ol>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Perpetual patrols</li>
|
||
<li>Endless background processes</li>
|
||
<li>Continuous monitoring</li>
|
||
</ul>
|
||
<p><strong>Warning:</strong> Infinite loops never return to parent. Ensure they’re appropriate for your use case.</p>
|
||
<h3 id="repeat-n-times-repeat-n"><a class="header" href="#repeat-n-times-repeat-n">Repeat N Times: <code>repeat N</code></a></h3>
|
||
<p>Repeats the child exactly N times, then returns success.</p>
|
||
<pre><code class="language-storybook">behavior CheckThreeTimes {
|
||
repeat(3) {
|
||
CheckDoor
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Counter = 0</li>
|
||
<li>Run child</li>
|
||
<li>Counter++</li>
|
||
<li>If counter < N, go to step 2</li>
|
||
<li>Return success</li>
|
||
</ol>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Fixed iteration counts</li>
|
||
<li>“Try three times then give up”</li>
|
||
<li>Deterministic looping</li>
|
||
</ul>
|
||
<h3 id="repeat-range-repeat-minmax"><a class="header" href="#repeat-range-repeat-minmax">Repeat Range: <code>repeat min..max</code></a></h3>
|
||
<p>Repeats the child between min and max times. At runtime, a specific count is selected within the range.</p>
|
||
<pre><code class="language-storybook">behavior SearchRandomly {
|
||
repeat(2..5) {
|
||
SearchArea
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Select count C randomly from [min, max]</li>
|
||
<li>Repeat child C times (as in <code>repeat N</code>)</li>
|
||
</ol>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Variable behavior</li>
|
||
<li>Procedural variation</li>
|
||
<li>Non-deterministic actions</li>
|
||
</ul>
|
||
<p><strong>Validation:</strong></p>
|
||
<ul>
|
||
<li>min ≥ 0</li>
|
||
<li>max ≥ min</li>
|
||
<li>Both must be integers</li>
|
||
</ul>
|
||
<h2 id="invert-decorator"><a class="header" href="#invert-decorator">Invert Decorator</a></h2>
|
||
<p>Inverts the child’s return value: success becomes failure, failure becomes success.</p>
|
||
<pre><code class="language-storybook">behavior AvoidEnemies {
|
||
invert {
|
||
IsEnemyNearby // Success if NOT nearby
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Truth table:</strong></p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Child returns</th><th>Decorator returns</th></tr></thead><tbody>
|
||
<tr><td>Success</td><td>Failure</td></tr>
|
||
<tr><td>Failure</td><td>Success</td></tr>
|
||
<tr><td>Running</td><td>Running (unchanged)</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Negating conditions (“if NOT X”)</li>
|
||
<li>Inverting success criteria</li>
|
||
<li>Converting “found” to “not found”</li>
|
||
</ul>
|
||
<p><strong>Example:</strong></p>
|
||
<pre><code class="language-storybook">behavior SafeExploration {
|
||
choose safe_actions {
|
||
// Only explore if NOT dangerous
|
||
then explore {
|
||
invert { IsDangerous }
|
||
ExploreArea
|
||
}
|
||
|
||
// Default: Stay put
|
||
Wait
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="retry-decorator"><a class="header" href="#retry-decorator">Retry Decorator</a></h2>
|
||
<p>Retries the child up to N times on failure. Returns success if any attempt succeeds, failure if all N attempts fail.</p>
|
||
<pre><code class="language-storybook">behavior PersistentDoor {
|
||
retry(5) {
|
||
OpenLockedDoor
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Attempts = 0</li>
|
||
<li>Run child</li>
|
||
<li>If child succeeds → return success</li>
|
||
<li>If child fails:
|
||
<ul>
|
||
<li>Attempts++</li>
|
||
<li>If attempts < N, go to step 2</li>
|
||
<li>Else return failure</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Unreliable actions (lockpicking, persuasion)</li>
|
||
<li>Network/resource operations</li>
|
||
<li>Probabilistic success</li>
|
||
</ul>
|
||
<p><strong>Example with context:</strong></p>
|
||
<pre><code class="language-storybook">behavior Thief_PickLock {
|
||
then attempt_entry {
|
||
// Try to pick lock (may fail)
|
||
retry(3) {
|
||
PickLock
|
||
}
|
||
|
||
// If succeeded, enter
|
||
EnterBuilding
|
||
|
||
// If failed after 3 tries, give up
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Validation:</strong></p>
|
||
<ul>
|
||
<li>N must be ≥ 1</li>
|
||
</ul>
|
||
<h2 id="timeout-decorator"><a class="header" href="#timeout-decorator">Timeout Decorator</a></h2>
|
||
<p>Fails the child if it doesn’t complete within the specified duration.</p>
|
||
<pre><code class="language-storybook">behavior TimeLimitedPuzzle {
|
||
timeout(30s) {
|
||
SolvePuzzle
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Start timer</li>
|
||
<li>Run child each tick</li>
|
||
<li>If child completes before timeout → return child’s result</li>
|
||
<li>If timer expires → return failure (interrupt child)</li>
|
||
</ol>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Time-limited actions</li>
|
||
<li>Preventing infinite loops</li>
|
||
<li>Enforcing deadlines</li>
|
||
</ul>
|
||
<p><strong>Duration formats:</strong></p>
|
||
<ul>
|
||
<li><code>5s</code> - 5 seconds</li>
|
||
<li><code>10m</code> - 10 minutes</li>
|
||
<li><code>2h</code> - 2 hours</li>
|
||
<li><code>3d</code> - 3 days</li>
|
||
</ul>
|
||
<p><strong>Example:</strong></p>
|
||
<pre><code class="language-storybook">behavior QuickDecision {
|
||
choose timed_choice {
|
||
// Give AI 5 seconds to find optimal move
|
||
timeout(5s) {
|
||
CalculateOptimalStrategy
|
||
}
|
||
|
||
// Fallback: Use simple heuristic
|
||
UseQuickHeuristic
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Notes:</strong></p>
|
||
<ul>
|
||
<li>Timer starts when decorator is first entered</li>
|
||
<li>Timer resets if decorator exits and re-enters</li>
|
||
<li>Child node should handle interruption gracefully</li>
|
||
</ul>
|
||
<h2 id="cooldown-decorator"><a class="header" href="#cooldown-decorator">Cooldown Decorator</a></h2>
|
||
<p>Prevents the child from running more than once per cooldown period. Fails immediately if called within cooldown.</p>
|
||
<pre><code class="language-storybook">behavior SpecialAbility {
|
||
cooldown(30s) {
|
||
FireCannon
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Check last execution time</li>
|
||
<li>If (current_time - last_time) < cooldown → return failure</li>
|
||
<li>Else:
|
||
<ul>
|
||
<li>Run child</li>
|
||
<li>Record current_time as last_time</li>
|
||
<li>Return child’s result</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Rate-limiting abilities</li>
|
||
<li>Resource cooldowns (spells, items)</li>
|
||
<li>Preventing spam</li>
|
||
</ul>
|
||
<p><strong>Example:</strong></p>
|
||
<pre><code class="language-storybook">behavior Mage_SpellCasting {
|
||
choose spells {
|
||
// Fireball: 10 second cooldown
|
||
cooldown(10s) {
|
||
CastFireball
|
||
}
|
||
|
||
// Lightning: 5 second cooldown
|
||
cooldown(5s) {
|
||
CastLightning
|
||
}
|
||
|
||
// Basic attack: No cooldown
|
||
MeleeAttack
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>State management:</strong></p>
|
||
<ul>
|
||
<li>Cooldown state persists across behavior tree ticks</li>
|
||
<li>Each cooldown decorator instance has independent state</li>
|
||
<li>Cooldown timers are per-entity (not global)</li>
|
||
</ul>
|
||
<h2 id="if-decorator"><a class="header" href="#if-decorator">If Decorator</a></h2>
|
||
<p>Only runs the child if the condition is true. Fails immediately if condition is false.</p>
|
||
<pre><code class="language-storybook">behavior ConditionalAttack {
|
||
if(health > 50) {
|
||
AggressiveAttack
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution:</strong></p>
|
||
<ol>
|
||
<li>Evaluate condition expression</li>
|
||
<li>If true → run child and return its result</li>
|
||
<li>If false → return failure (do not run child)</li>
|
||
</ol>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Preconditions (“only if X”)</li>
|
||
<li>Resource checks (“only if have mana”)</li>
|
||
<li>Safety checks (“only if safe”)</li>
|
||
</ul>
|
||
<p><strong>Expression syntax:</strong>
|
||
See <a href="reference/./17-expressions.html">Expression Language</a> for complete syntax.</p>
|
||
<p><strong>Examples:</strong></p>
|
||
<pre><code class="language-storybook">behavior GuardedActions {
|
||
choose options {
|
||
// Only attack if have weapon and enemy close
|
||
if(has_weapon and distance < 10) {
|
||
Attack
|
||
}
|
||
|
||
// Only heal if health below 50%
|
||
if(health < (max_health * 0.5)) {
|
||
Heal
|
||
}
|
||
|
||
// Only flee if outnumbered
|
||
if(enemy_count > ally_count) {
|
||
Flee
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Comparison with bare <code>if</code> conditions:</strong></p>
|
||
<pre><code class="language-storybook">// Using bare 'if' condition (checks every tick, no body)
|
||
then approach_and_attack {
|
||
if(enemy_nearby)
|
||
Approach
|
||
Attack
|
||
}
|
||
|
||
// Using 'if' decorator with body (precondition check, fails fast)
|
||
if(enemy_nearby) {
|
||
then do_attack {
|
||
Approach
|
||
Attack
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>The <code>if</code> decorator with a body is more efficient for gating expensive subtrees.</p>
|
||
<h2 id="force-result-decorators"><a class="header" href="#force-result-decorators">Force Result Decorators</a></h2>
|
||
<h3 id="succeed_always"><a class="header" href="#succeed_always"><code>succeed_always</code></a></h3>
|
||
<p>Always returns success, regardless of child’s actual result.</p>
|
||
<pre><code class="language-storybook">behavior TryOptionalTask {
|
||
succeed_always {
|
||
AttemptBonus // Even if fails, we don't care
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Optional tasks that shouldn’t block progress</li>
|
||
<li>Logging/monitoring actions</li>
|
||
<li>Best-effort operations</li>
|
||
</ul>
|
||
<p><strong>Example:</strong></p>
|
||
<pre><code class="language-storybook">behavior QuestSequence {
|
||
then main_quest {
|
||
TalkToNPC
|
||
|
||
// Try to find secret, but don't fail quest if not found
|
||
succeed_always {
|
||
SearchForSecretDoor
|
||
}
|
||
|
||
ReturnToQuestGiver
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="fail_always"><a class="header" href="#fail_always"><code>fail_always</code></a></h3>
|
||
<p>Always returns failure, regardless of child’s actual result.</p>
|
||
<pre><code class="language-storybook">behavior TestFailure {
|
||
fail_always {
|
||
AlwaysSucceedsAction // But we force it to fail
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Use cases:</strong></p>
|
||
<ul>
|
||
<li>Testing/debugging</li>
|
||
<li>Forcing alternative paths</li>
|
||
<li>Disabling branches temporarily</li>
|
||
</ul>
|
||
<p><strong>Example:</strong></p>
|
||
<pre><code class="language-storybook">behavior UnderConstruction {
|
||
choose abilities {
|
||
// Temporarily disabled feature
|
||
fail_always {
|
||
NewExperimentalAbility
|
||
}
|
||
|
||
// Fallback to old ability
|
||
ClassicAbility
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="combining-decorators-1"><a class="header" href="#combining-decorators-1">Combining Decorators</a></h2>
|
||
<p>Decorators can nest to create complex behaviors:</p>
|
||
<pre><code class="language-storybook">behavior ComplexPattern {
|
||
// Repeat 3 times, each with 10 second timeout
|
||
repeat(3) {
|
||
timeout(10s) {
|
||
SolveSubproblem
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>More complex nesting:</strong></p>
|
||
<pre><code class="language-storybook">behavior ResilientAction {
|
||
// If: Only if health > 30
|
||
if(health > 30) {
|
||
// Timeout: Must complete in 20 seconds
|
||
timeout(20s) {
|
||
// Retry: Try up to 5 times
|
||
retry(5) {
|
||
// Cooldown: Can only run once per minute
|
||
cooldown(1m) {
|
||
PerformComplexAction
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Execution order:</strong> Outside → Inside</p>
|
||
<ol>
|
||
<li>If checks condition</li>
|
||
<li>Timeout starts timer</li>
|
||
<li>Retry begins first attempt</li>
|
||
<li>Cooldown checks last execution time</li>
|
||
<li>Child action runs</li>
|
||
</ol>
|
||
<h2 id="duration-syntax"><a class="header" href="#duration-syntax">Duration Syntax</a></h2>
|
||
<p>Timeout and cooldown decorators use duration literals:</p>
|
||
<pre><code class="language-bnf"><duration-literal> ::= <number> <unit>
|
||
|
||
<unit> ::= "s" // seconds
|
||
| "m" // minutes
|
||
| "h" // hours
|
||
| "d" // days
|
||
|
||
<number> ::= <digit>+
|
||
</code></pre>
|
||
<p><strong>Examples:</strong></p>
|
||
<ul>
|
||
<li><code>5s</code> - 5 seconds</li>
|
||
<li><code>30s</code> - 30 seconds</li>
|
||
<li><code>2m</code> - 2 minutes</li>
|
||
<li><code>10m</code> - 10 minutes</li>
|
||
<li><code>1h</code> - 1 hour</li>
|
||
<li><code>24h</code> - 24 hours</li>
|
||
<li><code>7d</code> - 7 days</li>
|
||
</ul>
|
||
<p><strong>Validation:</strong></p>
|
||
<ul>
|
||
<li>Number must be positive integer</li>
|
||
<li>No compound durations (use <code>120s</code> not <code>2m</code> if runtime expects seconds)</li>
|
||
<li>No fractional units (<code>1.5m</code> not allowed; use <code>90s</code>)</li>
|
||
</ul>
|
||
<h2 id="comparison-table"><a class="header" href="#comparison-table">Comparison Table</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Decorator</th><th>Affects Success</th><th>Affects Failure</th><th>Affects Running</th><th>Stateful</th></tr></thead><tbody>
|
||
<tr><td><code>repeat</code></td><td>Repeat</td><td>Repeat</td><td>Wait</td><td>Yes (counter)</td></tr>
|
||
<tr><td><code>repeat N</code></td><td>Repeat</td><td>Repeat</td><td>Wait</td><td>Yes (counter)</td></tr>
|
||
<tr><td><code>repeat min..max</code></td><td>Repeat</td><td>Repeat</td><td>Wait</td><td>Yes (counter)</td></tr>
|
||
<tr><td><code>invert</code></td><td>→ Failure</td><td>→ Success</td><td>Unchanged</td><td>No</td></tr>
|
||
<tr><td><code>retry N</code></td><td>→ Success</td><td>Retry or fail</td><td>Wait</td><td>Yes (attempts)</td></tr>
|
||
<tr><td><code>timeout dur</code></td><td>→ Success</td><td>→ Success</td><td>→ Failure if expired</td><td>Yes (timer)</td></tr>
|
||
<tr><td><code>cooldown dur</code></td><td>→ Success</td><td>→ Success</td><td>→ Success</td><td>Yes (last time)</td></tr>
|
||
<tr><td><code>if(expr)</code></td><td>→ Success</td><td>→ Success</td><td>→ Success</td><td>No</td></tr>
|
||
<tr><td><code>succeed_always</code></td><td>→ Success</td><td>→ Success</td><td>→ Success</td><td>No</td></tr>
|
||
<tr><td><code>fail_always</code></td><td>→ Failure</td><td>→ Failure</td><td>→ Failure</td><td>No</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Stateful decorators</strong> maintain state across ticks. <strong>Stateless decorators</strong> evaluate fresh every tick.</p>
|
||
<h2 id="validation-rules-2"><a class="header" href="#validation-rules-2">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>Child required</strong>: All decorators must have exactly one child node</li>
|
||
<li><strong>Repeat count</strong>: <code>repeat N</code> requires N ≥ 0</li>
|
||
<li><strong>Repeat range</strong>: <code>repeat min..max</code> requires 0 ≤ min ≤ max</li>
|
||
<li><strong>Retry count</strong>: <code>retry N</code> requires N ≥ 1</li>
|
||
<li><strong>Duration positive</strong>: Timeout/cooldown durations must be > 0</li>
|
||
<li><strong>Duration format</strong>: Must match <code><number><unit></code> (e.g., <code>10s</code>, <code>5m</code>)</li>
|
||
<li><strong>Guard expression</strong>: Guard condition must be valid expression</li>
|
||
<li><strong>No empty decorators</strong>: <code>repeat { }</code> is invalid (missing child)</li>
|
||
</ol>
|
||
<h2 id="use-cases-by-category"><a class="header" href="#use-cases-by-category">Use Cases by Category</a></h2>
|
||
<h3 id="timing-control"><a class="header" href="#timing-control">Timing Control</a></h3>
|
||
<ul>
|
||
<li><strong>timeout</strong>: Prevent infinite loops, enforce time limits</li>
|
||
<li><strong>cooldown</strong>: Rate-limit abilities, prevent spam</li>
|
||
<li><strong>repeat</strong>: Continuous processes, patrols</li>
|
||
</ul>
|
||
<h3 id="reliability"><a class="header" href="#reliability">Reliability</a></h3>
|
||
<ul>
|
||
<li><strong>retry</strong>: Handle unreliable actions, probabilistic success</li>
|
||
<li><strong>if</strong>: Precondition checks, resource validation</li>
|
||
<li><strong>succeed_always</strong>: Optional tasks, best-effort</li>
|
||
</ul>
|
||
<h3 id="logic-control"><a class="header" href="#logic-control">Logic Control</a></h3>
|
||
<ul>
|
||
<li><strong>invert</strong>: Negate conditions, flip results</li>
|
||
<li><strong>fail_always</strong>: Disable branches, testing</li>
|
||
</ul>
|
||
<h3 id="iteration"><a class="header" href="#iteration">Iteration</a></h3>
|
||
<ul>
|
||
<li><strong>repeat N</strong>: Fixed loops, deterministic behavior</li>
|
||
<li><strong>repeat min..max</strong>: Variable loops, procedural variation</li>
|
||
</ul>
|
||
<h2 id="best-practices-1"><a class="header" href="#best-practices-1">Best Practices</a></h2>
|
||
<h3 id="1-use-guards-for-expensive-checks"><a class="header" href="#1-use-guards-for-expensive-checks">1. Use Guards for Expensive Checks</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">then expensive {
|
||
if(complex_condition)
|
||
ExpensiveOperation
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">if(complex_condition) {
|
||
ExpensiveOperation // Only runs if condition passes
|
||
}
|
||
</code></pre>
|
||
<h3 id="2-combine-timeout-with-retry"><a class="header" href="#2-combine-timeout-with-retry">2. Combine Timeout with Retry</a></h3>
|
||
<p><strong>Avoid:</strong> Infinite retry loops</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">timeout(30s) {
|
||
retry(5) {
|
||
UnreliableAction
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-use-cooldowns-for-rate-limiting"><a class="header" href="#3-use-cooldowns-for-rate-limiting">3. Use Cooldowns for Rate Limiting</a></h3>
|
||
<p><strong>Avoid:</strong> Manual timing in actions</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">cooldown(10s) {
|
||
FireCannon
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-invert-for-readable-conditions"><a class="header" href="#4-invert-for-readable-conditions">4. Invert for Readable Conditions</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">choose options {
|
||
then branch_a {
|
||
if(not is_dangerous)
|
||
Explore
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">choose options {
|
||
then branch_a {
|
||
invert { IsDangerous }
|
||
Explore
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="5-succeed_always-for-optional-tasks"><a class="header" href="#5-succeed_always-for-optional-tasks">5. succeed_always for Optional Tasks</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">then quest {
|
||
MainTask
|
||
choose optional {
|
||
BonusTask
|
||
DoNothing // Awkward fallback
|
||
}
|
||
NextTask
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">then quest {
|
||
MainTask
|
||
succeed_always { BonusTask } // Try bonus, don't fail quest
|
||
NextTask
|
||
}
|
||
</code></pre>
|
||
<h2 id="cross-references-2"><a class="header" href="#cross-references-2">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> - Using decorators in behavior trees</li>
|
||
<li><a href="reference/./17-expressions.html">Expression Language</a> - Guard condition syntax</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - Duration literals</li>
|
||
<li><a href="reference/../advanced/20-patterns.html">Design Patterns</a> - Common decorator patterns</li>
|
||
</ul>
|
||
<h2 id="related-concepts-2"><a class="header" href="#related-concepts-2">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Composability</strong>: Decorators can nest for complex control flow</li>
|
||
<li><strong>Separation of concerns</strong>: Decorators handle control flow, children handle logic</li>
|
||
<li><strong>State management</strong>: Stateful decorators (repeat, retry, timeout, cooldown) persist across ticks</li>
|
||
<li><strong>Performance</strong>: Guards prevent unnecessary child execution</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="life-arcs-1"><a class="header" href="#life-arcs-1">Life Arcs</a></h1>
|
||
<p>Life arcs are state machines that model how characters or other entities evolve over time. They define discrete states and the conditions under which an entity transitions between states. Life arcs capture character development, quest progress, relationship phases, and any other finite-state processes.</p>
|
||
<h2 id="what-is-a-life-arc-1"><a class="header" href="#what-is-a-life-arc-1">What is a Life Arc?</a></h2>
|
||
<p>A life arc is a finite state machine (FSM) composed of:</p>
|
||
<ul>
|
||
<li><strong>States</strong>: Discrete phases or modes (e.g., “happy”, “angry”, “sleeping”)</li>
|
||
<li><strong>Transitions</strong>: Conditional edges between states (e.g., “when health < 20, go to ‘fleeing’”)</li>
|
||
<li><strong>On-enter actions</strong>: Field updates that occur when entering a state</li>
|
||
</ul>
|
||
<pre><code>[State A] --condition--> [State B] --condition--> [State C]
|
||
| | |
|
||
on enter on enter on enter
|
||
(set fields) (set fields) (set fields)
|
||
</code></pre>
|
||
<p>Life arcs run continuously, evaluating transitions every tick and moving between states as conditions become true.</p>
|
||
<h2 id="syntax-3"><a class="header" href="#syntax-3">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><life-arc-decl> ::= "life_arc" <identifier> <body>
|
||
|
||
<body> ::= "{" <prose-blocks>* <state>+ "}"
|
||
|
||
<state> ::= "state" <identifier> "{" <state-body>* "}"
|
||
|
||
<state-body> ::= <on-enter-block>
|
||
| <transition>
|
||
| <prose-block>
|
||
|
||
<on-enter-block> ::= "on" "enter" "{" <field>+ "}"
|
||
|
||
<transition> ::= "on" <expression> "->" <identifier>
|
||
|
||
<prose-block> ::= "---" <identifier> <content> "---"
|
||
</code></pre>
|
||
<h2 id="states-1"><a class="header" href="#states-1">States</a></h2>
|
||
<p>A state represents a discrete phase in an entity’s lifecycle.</p>
|
||
<h3 id="basic-state"><a class="header" href="#basic-state">Basic State</a></h3>
|
||
<pre><code class="language-storybook">life_arc SimpleArc {
|
||
state idle {
|
||
---narrative
|
||
The character is doing nothing, waiting for something to happen.
|
||
---
|
||
}
|
||
|
||
state active {
|
||
---narrative
|
||
The character is engaged in their primary activity.
|
||
---
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="state-names"><a class="header" href="#state-names">State Names</a></h3>
|
||
<p>State names are identifiers that must be unique within the life arc. Use descriptive names:</p>
|
||
<ul>
|
||
<li><strong>Good</strong>: <code>idle</code>, <code>combat</code>, <code>sleeping</code>, <code>enlightened</code></li>
|
||
<li><strong>Avoid</strong>: <code>state1</code>, <code>s</code>, <code>temp</code></li>
|
||
</ul>
|
||
<h2 id="on-enter-actions-1"><a class="header" href="#on-enter-actions-1">On-Enter Actions</a></h2>
|
||
<p>When entering a state, the <code>on enter</code> block updates entity fields.</p>
|
||
<h3 id="syntax-4"><a class="header" href="#syntax-4">Syntax</a></h3>
|
||
<pre><code class="language-storybook">state state_name {
|
||
on enter {
|
||
EntityName.field_name: value
|
||
EntityName.other_field: other_value
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="examples-1"><a class="header" href="#examples-1">Examples</a></h3>
|
||
<pre><code class="language-storybook">life_arc CharacterMood {
|
||
state happy {
|
||
on enter {
|
||
Martha.emotional_state: "happy"
|
||
Martha.energy: 100
|
||
}
|
||
}
|
||
|
||
state tired {
|
||
on enter {
|
||
Martha.emotional_state: "exhausted"
|
||
Martha.energy: 20
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="field-update-semantics"><a class="header" href="#field-update-semantics">Field Update Semantics</a></h3>
|
||
<ul>
|
||
<li><strong>Target</strong>: <code>EntityName.field_name</code> must reference an existing character/entity</li>
|
||
<li><strong>Value</strong>: Any valid <a href="reference/./18-value-types.html">value type</a></li>
|
||
<li><strong>Effect</strong>: Field is set to the specified value when state is entered</li>
|
||
<li><strong>Timing</strong>: Happens immediately upon transition to the state</li>
|
||
</ul>
|
||
<h3 id="multiple-field-updates"><a class="header" href="#multiple-field-updates">Multiple Field Updates</a></h3>
|
||
<pre><code class="language-storybook">state exhausted_baker {
|
||
on enter {
|
||
Martha.energy: 0.1
|
||
Martha.mood: "stressed"
|
||
Martha.quality_output: 0.7
|
||
Martha.needs_break: true
|
||
}
|
||
|
||
---narrative
|
||
After a sixteen-hour shift during the harvest festival,
|
||
Martha's hands are shaking. She knows her bread quality is
|
||
suffering and reluctantly steps away from the oven.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="transitions-1"><a class="header" href="#transitions-1">Transitions</a></h2>
|
||
<p>Transitions define conditional edges between states. When a transition’s condition becomes true, the state machine moves to the target state.</p>
|
||
<h3 id="syntax-5"><a class="header" href="#syntax-5">Syntax</a></h3>
|
||
<pre><code class="language-storybook">state source_state {
|
||
on condition_expression -> target_state
|
||
on another_condition -> another_target
|
||
}
|
||
</code></pre>
|
||
<h3 id="basic-transitions"><a class="header" href="#basic-transitions">Basic Transitions</a></h3>
|
||
<pre><code class="language-storybook">life_arc Combat {
|
||
state fighting {
|
||
on health < 20 -> fleeing
|
||
on enemy_defeated -> victorious
|
||
}
|
||
|
||
state fleeing {
|
||
on safe_distance_reached -> idle
|
||
}
|
||
|
||
state victorious {
|
||
on celebration_complete -> idle
|
||
}
|
||
|
||
state idle {}
|
||
}
|
||
</code></pre>
|
||
<h3 id="expression-conditions"><a class="header" href="#expression-conditions">Expression Conditions</a></h3>
|
||
<p>Transitions use the full <a href="reference/./17-expressions.html">expression language</a>:</p>
|
||
<p><strong>Comparisons:</strong></p>
|
||
<pre><code class="language-storybook">on health < 20 -> low_health
|
||
on distance > 100 -> far_away
|
||
on count == 0 -> empty
|
||
</code></pre>
|
||
<p><strong>Boolean fields:</strong></p>
|
||
<pre><code class="language-storybook">on is_hostile -> combat
|
||
on completed -> done
|
||
on not ready -> waiting
|
||
</code></pre>
|
||
<p><strong>Logical operators:</strong></p>
|
||
<pre><code class="language-storybook">on health < 20 and not has_potion -> desperate
|
||
on is_day or is_lit -> visible
|
||
</code></pre>
|
||
<p><strong>Complex conditions:</strong></p>
|
||
<pre><code class="language-storybook">on (health < 50 and enemy_count > 3) or surrounded -> retreat
|
||
on forall e in enemies: e.defeated -> victory
|
||
</code></pre>
|
||
<h3 id="multiple-transitions"><a class="header" href="#multiple-transitions">Multiple Transitions</a></h3>
|
||
<p>A state can have multiple outgoing transitions:</p>
|
||
<pre><code class="language-storybook">state monitoring {
|
||
on status is "active" -> active_state
|
||
on status is "inactive" -> inactive_state
|
||
on status is "error" -> error_state
|
||
on shutdown_requested -> shutting_down
|
||
}
|
||
</code></pre>
|
||
<p><strong>Evaluation order:</strong></p>
|
||
<ul>
|
||
<li>Transitions are evaluated in declaration order (top to bottom)</li>
|
||
<li>First transition with a true condition is taken</li>
|
||
<li>If no conditions are true, state remains unchanged</li>
|
||
</ul>
|
||
<h3 id="self-transitions"><a class="header" href="#self-transitions">Self-Transitions</a></h3>
|
||
<p>A state can transition to itself:</p>
|
||
<pre><code class="language-storybook">state patrolling {
|
||
on enemy_spotted -> combat
|
||
on checkpoint_reached -> patrolling // Reset patrol state
|
||
}
|
||
</code></pre>
|
||
<h2 id="complete-examples-1"><a class="header" href="#complete-examples-1">Complete Examples</a></h2>
|
||
<h3 id="elenas-career-journey"><a class="header" href="#elenas-career-journey">Elena’s Career Journey</a></h3>
|
||
<pre><code class="language-storybook">life_arc ElenaCareer {
|
||
---description
|
||
Tracks Elena's progression from nervous apprentice to confident
|
||
master baker. Each state represents a key phase of her career.
|
||
---
|
||
|
||
state early_apprentice {
|
||
on enter {
|
||
Elena.skill_level: novice
|
||
Elena.confidence: timid
|
||
}
|
||
|
||
on recipes_mastered > 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 > 30 -> senior_journeyman
|
||
|
||
---narrative
|
||
Elena runs the morning shift alone while Martha handles
|
||
special orders. Customers start asking for "Elena's rolls."
|
||
She begins experimenting with her own recipes.
|
||
---
|
||
}
|
||
|
||
state senior_journeyman {
|
||
on enter {
|
||
Elena.skill_level: advanced
|
||
Elena.confidence: steady
|
||
}
|
||
|
||
on recipes_mastered > 50 -> master
|
||
|
||
---narrative
|
||
Elena develops her signature recipe: rosemary olive bread
|
||
that becomes the bakery's bestseller. She handles difficult
|
||
customers with grace and trains new helpers.
|
||
---
|
||
}
|
||
|
||
state master {
|
||
on enter {
|
||
Elena.skill_level: master
|
||
Elena.confidence: commanding
|
||
Elena.can_teach: true
|
||
}
|
||
|
||
---narrative
|
||
Master Baker Elena. She has earned it. The guild acknowledges
|
||
her mastery, and Martha beams with pride. Elena begins
|
||
mentoring her own apprentice.
|
||
---
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="quest-progress"><a class="header" href="#quest-progress">Quest Progress</a></h3>
|
||
<pre><code class="language-storybook">life_arc HeroQuest {
|
||
state not_started {
|
||
on talked_to_elder -> received_quest
|
||
}
|
||
|
||
state received_quest {
|
||
on enter {
|
||
Hero.quest_active: true
|
||
Hero.quest_step: 0
|
||
}
|
||
|
||
on found_first_artifact -> collecting
|
||
}
|
||
|
||
state collecting {
|
||
on enter {
|
||
Hero.quest_step: 1
|
||
}
|
||
|
||
on artifact_count == 3 -> returning
|
||
on failed_trial -> failed
|
||
}
|
||
|
||
state returning {
|
||
on enter {
|
||
Hero.quest_step: 2
|
||
}
|
||
|
||
on reached_elder -> completed
|
||
}
|
||
|
||
state completed {
|
||
on enter {
|
||
Hero.quest_active: false
|
||
Hero.quest_completed: true
|
||
Hero.reputation: 100
|
||
}
|
||
|
||
---narrative
|
||
The hero returns triumphant, artifacts in hand. The elder
|
||
bestows great rewards and the village celebrates.
|
||
---
|
||
}
|
||
|
||
state failed {
|
||
on enter {
|
||
Hero.quest_active: false
|
||
Hero.quest_failed: true
|
||
}
|
||
|
||
on retry_accepted -> received_quest
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="name-checker-equality-examples"><a class="header" href="#name-checker-equality-examples">Name Checker (Equality Examples)</a></h3>
|
||
<pre><code class="language-storybook">life_arc NameCheck {
|
||
state checking {
|
||
on name is "Martha" -> found_martha
|
||
on name is "Jane" -> found_jane
|
||
}
|
||
|
||
state found_martha {
|
||
on ready -> checking
|
||
}
|
||
|
||
state found_jane {
|
||
on ready -> checking
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="status-monitor"><a class="header" href="#status-monitor">Status Monitor</a></h3>
|
||
<pre><code class="language-storybook">life_arc StatusMonitor {
|
||
state monitoring {
|
||
on status is active -> active_state
|
||
on status is inactive -> inactive_state
|
||
on status is error -> error_state
|
||
}
|
||
|
||
state active_state {
|
||
on enter {
|
||
System.led_color: "green"
|
||
}
|
||
|
||
on status is inactive -> inactive_state
|
||
on status is error -> error_state
|
||
}
|
||
|
||
state inactive_state {
|
||
on enter {
|
||
System.led_color: "yellow"
|
||
}
|
||
|
||
on status is active -> active_state
|
||
on status is error -> error_state
|
||
}
|
||
|
||
state error_state {
|
||
on enter {
|
||
System.led_color: "red"
|
||
System.alarm: true
|
||
}
|
||
|
||
on error_cleared -> monitoring
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="character-mood-swings"><a class="header" href="#character-mood-swings">Character Mood Swings</a></h3>
|
||
<pre><code class="language-storybook">life_arc MoodSwings {
|
||
state neutral {
|
||
on provoked -> angry
|
||
on complimented -> happy
|
||
on tired -> sleepy
|
||
}
|
||
|
||
state angry {
|
||
on enter {
|
||
Character.aggression: 0.9
|
||
Character.willingness_to_talk: 0.1
|
||
}
|
||
|
||
on calmed_down -> neutral
|
||
on escalated -> furious
|
||
}
|
||
|
||
state furious {
|
||
on enter {
|
||
Character.aggression: 1.0
|
||
Character.will_attack: true
|
||
}
|
||
|
||
on timeout_elapsed -> angry
|
||
on apologized_to -> neutral
|
||
}
|
||
|
||
state happy {
|
||
on enter {
|
||
Character.aggression: 0.0
|
||
Character.willingness_to_talk: 1.0
|
||
Character.gives_discounts: true
|
||
}
|
||
|
||
on insulted -> neutral
|
||
on bored -> neutral
|
||
}
|
||
|
||
state sleepy {
|
||
on enter {
|
||
Character.responsiveness: 0.2
|
||
}
|
||
|
||
on woke_up -> neutral
|
||
on fell_asleep -> sleeping
|
||
}
|
||
|
||
state sleeping {
|
||
on enter {
|
||
Character.responsiveness: 0.0
|
||
Character.is_vulnerable: true
|
||
}
|
||
|
||
on woke_up -> neutral
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="execution-semantics-1"><a class="header" href="#execution-semantics-1">Execution Semantics</a></h2>
|
||
<h3 id="tick-based-evaluation-1"><a class="header" href="#tick-based-evaluation-1">Tick-Based Evaluation</a></h3>
|
||
<p>Life arcs evaluate transitions every “tick” (typically once per frame):</p>
|
||
<ol>
|
||
<li><strong>Current state</strong>: Start in the current state</li>
|
||
<li><strong>Evaluate transitions</strong>: Check each outgoing transition’s condition (in order)</li>
|
||
<li><strong>First true transition</strong>: Take the first transition with a true condition</li>
|
||
<li><strong>Enter new state</strong>: Execute the new state’s <code>on enter</code> block</li>
|
||
<li><strong>Repeat next tick</strong></li>
|
||
</ol>
|
||
<h3 id="transition-priority-1"><a class="header" href="#transition-priority-1">Transition Priority</a></h3>
|
||
<p>When multiple transitions could fire, the <strong>first one in declaration order</strong> is taken:</p>
|
||
<pre><code class="language-storybook">state combat {
|
||
on health < 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> transition fires (even though <code>defensive</code> condition is also true).</p>
|
||
<h3 id="state-persistence"><a class="header" href="#state-persistence">State Persistence</a></h3>
|
||
<p>The current state persists across ticks until a transition fires.</p>
|
||
<pre><code class="language-storybook">state waiting {
|
||
on signal_received -> active
|
||
}
|
||
</code></pre>
|
||
<p>The entity remains in <code>waiting</code> state indefinitely until <code>signal_received</code> becomes true.</p>
|
||
<h3 id="on-enter-execution"><a class="header" href="#on-enter-execution">On-Enter Execution</a></h3>
|
||
<p><code>on enter</code> blocks execute <strong>once</strong> when entering the state, not every tick:</p>
|
||
<pre><code class="language-storybook">state combat {
|
||
on enter {
|
||
Character.weapon_drawn: true // Runs once when entering combat
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="validation-rules-3"><a class="header" href="#validation-rules-3">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>At least one state</strong>: Life arc must contain at least one state</li>
|
||
<li><strong>Unique state names</strong>: State names must be unique within the life arc</li>
|
||
<li><strong>Valid transitions</strong>: Transition targets must reference defined states</li>
|
||
<li><strong>No orphan states</strong>: All states should be reachable (warning, not error)</li>
|
||
<li><strong>Expression validity</strong>: Transition conditions must be well-formed expressions</li>
|
||
<li><strong>Field targets</strong>: On-enter field updates must reference valid entities/fields</li>
|
||
<li><strong>No cyclic immediate transitions</strong>: Avoid transitions that fire immediately in a loop</li>
|
||
</ol>
|
||
<h2 id="design-patterns"><a class="header" href="#design-patterns">Design Patterns</a></h2>
|
||
<h3 id="1-hub-and-spoke"><a class="header" href="#1-hub-and-spoke">1. Hub-and-Spoke</a></h3>
|
||
<p>A central “hub” state with transitions to specialized states:</p>
|
||
<pre><code class="language-storybook">life_arc HubPattern {
|
||
state idle {
|
||
on combat_triggered -> combat
|
||
on quest_accepted -> questing
|
||
on entered_shop -> shopping
|
||
}
|
||
|
||
state combat {
|
||
on combat_ended -> idle
|
||
}
|
||
|
||
state questing {
|
||
on quest_completed -> idle
|
||
}
|
||
|
||
state shopping {
|
||
on left_shop -> idle
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="2-linear-progression"><a class="header" href="#2-linear-progression">2. Linear Progression</a></h3>
|
||
<p>States form a linear sequence (quests, tutorials):</p>
|
||
<pre><code class="language-storybook">life_arc Tutorial {
|
||
state intro {
|
||
on clicked_start -> 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="3-cyclic-states"><a class="header" href="#3-cyclic-states">3. Cyclic States</a></h3>
|
||
<p>States form a cycle (day/night, seasons):</p>
|
||
<pre><code class="language-storybook">life_arc DayNightCycle {
|
||
state dawn {
|
||
on hour >= 8 -> day
|
||
}
|
||
|
||
state day {
|
||
on hour >= 18 -> dusk
|
||
}
|
||
|
||
state dusk {
|
||
on hour >= 20 -> night
|
||
}
|
||
|
||
state night {
|
||
on hour >= 6 -> dawn
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-hierarchical-simulated"><a class="header" href="#4-hierarchical-simulated">4. Hierarchical (Simulated)</a></h3>
|
||
<p>Use multiple life arcs for hierarchical state:</p>
|
||
<pre><code class="language-storybook">// Top-level life arc
|
||
life_arc CharacterState {
|
||
state alive {
|
||
on health <= 0 -> dead
|
||
}
|
||
|
||
state dead {}
|
||
}
|
||
|
||
// Nested life arc (only active when alive)
|
||
life_arc CombatState {
|
||
state idle {
|
||
on enemy_nearby -> combat
|
||
}
|
||
|
||
state combat {
|
||
on enemy_defeated -> idle
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="best-practices-2"><a class="header" href="#best-practices-2">Best Practices</a></h2>
|
||
<h3 id="1-use-descriptive-state-names"><a class="header" href="#1-use-descriptive-state-names">1. Use Descriptive State Names</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">state s1 { ... }
|
||
state s2 { ... }
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">state waiting_for_player { ... }
|
||
state engaged_in_combat { ... }
|
||
</code></pre>
|
||
<h3 id="2-add-narrative-prose-blocks"><a class="header" href="#2-add-narrative-prose-blocks">2. Add Narrative Prose Blocks</a></h3>
|
||
<pre><code class="language-storybook">state master_baker {
|
||
---narrative
|
||
Master Baker Elena. She has earned it. The guild acknowledges
|
||
her mastery, and Martha beams with pride. Elena begins
|
||
mentoring her own apprentice.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-order-transitions-by-priority"><a class="header" href="#3-order-transitions-by-priority">3. Order Transitions by Priority</a></h3>
|
||
<pre><code class="language-storybook">state health_check {
|
||
on health <= 0 -> dead // Most urgent
|
||
on health < 20 -> critical // Very urgent
|
||
on health < 50 -> wounded // Moderate
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-avoid-orphan-states"><a class="header" href="#4-avoid-orphan-states">4. Avoid Orphan States</a></h3>
|
||
<p>Ensure all states are reachable from the initial state.</p>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">life_arc Broken {
|
||
state start {
|
||
on ready -> middle
|
||
}
|
||
|
||
state middle {
|
||
on done -> end
|
||
}
|
||
|
||
state unreachable {} // No transition leads here!
|
||
|
||
state end {}
|
||
}
|
||
</code></pre>
|
||
<h3 id="5-use-on-enter-for-state-initialization"><a class="header" href="#5-use-on-enter-for-state-initialization">5. Use On-Enter for State Initialization</a></h3>
|
||
<pre><code class="language-storybook">state combat {
|
||
on enter {
|
||
Character.weapon_drawn: true
|
||
Character.combat_stance: "aggressive"
|
||
Character.target: nearest_enemy
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="cross-references-3"><a class="header" href="#cross-references-3">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> - Characters can have associated life arcs</li>
|
||
<li><a href="reference/./17-expressions.html">Expression Language</a> - Transition condition syntax</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - On-enter field value types</li>
|
||
<li><a href="reference/./19-validation.html">Validation Rules</a> - Life arc validation constraints</li>
|
||
</ul>
|
||
<h2 id="related-concepts-3"><a class="header" href="#related-concepts-3">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Finite State Machines (FSM)</strong>: Life arcs are FSMs</li>
|
||
<li><strong>Character development</strong>: Track character growth over time</li>
|
||
<li><strong>Quest states</strong>: Model quest progression</li>
|
||
<li><strong>Mood systems</strong>: Model emotional states</li>
|
||
<li><strong>Lifecycle modeling</strong>: Birth, growth, aging, death</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="schedules"><a class="header" href="#schedules">Schedules</a></h1>
|
||
<p>Schedules define time-based routines for characters and institutions. They specify what activities occur during specific time ranges, support seasonal variations, recurring events, and template composition. Schedules enable rich temporal behavior from simple daily routines to complex year-long patterns.</p>
|
||
<h2 id="what-is-a-schedule"><a class="header" href="#what-is-a-schedule">What is a Schedule?</a></h2>
|
||
<p>A schedule is a collection of time blocks that define activities throughout a day, week, season, or year:</p>
|
||
<ul>
|
||
<li><strong>Blocks</strong>: Time ranges (e.g., <code>06:00 - 14:00</code>) with associated activities/behaviors</li>
|
||
<li><strong>Temporal constraints</strong>: When blocks apply (season, day of week, month)</li>
|
||
<li><strong>Recurrence patterns</strong>: Repeating events (e.g., “Market Day every Earthday”)</li>
|
||
<li><strong>Composition</strong>: Schedules can extend and override other schedules</li>
|
||
</ul>
|
||
<pre><code>Schedule: BakerySchedule
|
||
├─ Block: 06:00 - 08:00 → prepare_dough
|
||
├─ Block: 08:00 - 14:00 → serve_customers
|
||
├─ Recurrence: Market Day (on Earthday) → special_market_hours
|
||
└─ Extends: BaseBusiness (inherits closing hours)
|
||
</code></pre>
|
||
<h2 id="syntax-6"><a class="header" href="#syntax-6">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><schedule-decl> ::= "schedule" <identifier> <extends-clause>? <body>
|
||
|
||
<extends-clause> ::= "extends" <identifier>
|
||
|
||
<body> ::= "{" <schedule-item>* "}"
|
||
|
||
<schedule-item> ::= <schedule-block>
|
||
| <recurrence-pattern>
|
||
|
||
<schedule-block> ::= "block" <block-name>? "{" <block-content>+ "}"
|
||
|
||
<block-name> ::= <identifier>
|
||
|
||
<block-content> ::= <time-range>
|
||
| "action" ":" <qualified-path>
|
||
| <temporal-constraint>
|
||
| <field>
|
||
|
||
<time-range> ::= <time> "-" <time>
|
||
|
||
<temporal-constraint> ::= "on" <temporal-spec>
|
||
|
||
<temporal-spec> ::= "season" <identifier>
|
||
| "day" <identifier>
|
||
| "month" <identifier>
|
||
| "dates" <string> ".." <string>
|
||
|
||
<recurrence-pattern> ::= "recurs" <identifier> <temporal-constraint> "{" <schedule-block>+ "}"
|
||
</code></pre>
|
||
<h2 id="schedule-blocks"><a class="header" href="#schedule-blocks">Schedule Blocks</a></h2>
|
||
<p>A schedule block defines an activity during a specific time range.</p>
|
||
<h3 id="basic-block"><a class="header" href="#basic-block">Basic Block</a></h3>
|
||
<pre><code class="language-storybook">schedule SimpleBaker {
|
||
block {
|
||
06:00 - 14:00
|
||
action: baking::prepare_and_sell
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="named-block"><a class="header" href="#named-block">Named Block</a></h3>
|
||
<p>Named blocks support the override system:</p>
|
||
<pre><code class="language-storybook">schedule BakeryBase {
|
||
block work {
|
||
09:00 - 17:00
|
||
action: baking::standard_work
|
||
}
|
||
}
|
||
|
||
schedule EarlyBaker extends BakeryBase {
|
||
block work { // Override by name
|
||
05:00 - 13:00
|
||
action: baking::early_shift
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="block-content"><a class="header" href="#block-content">Block Content</a></h3>
|
||
<p>Blocks can contain:</p>
|
||
<ol>
|
||
<li><strong>Time range</strong> (required): <code>HH:MM - HH:MM</code></li>
|
||
<li><strong>Action</strong> (optional): Behavior tree reference</li>
|
||
<li><strong>Temporal constraint</strong> (optional): When the block applies</li>
|
||
<li><strong>Fields</strong> (optional): Additional metadata</li>
|
||
</ol>
|
||
<pre><code class="language-storybook">schedule CompleteBlock {
|
||
block morning_work {
|
||
06:00 - 12:00
|
||
action: work::morning_routine
|
||
on season summer
|
||
location: "Outdoor Market"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="time-ranges"><a class="header" href="#time-ranges">Time Ranges</a></h2>
|
||
<p>Time ranges use 24-hour clock format.</p>
|
||
<h3 id="syntax-7"><a class="header" href="#syntax-7">Syntax</a></h3>
|
||
<pre><code class="language-storybook">HH:MM - HH:MM
|
||
</code></pre>
|
||
<ul>
|
||
<li><strong>HH</strong>: Hour (00-23)</li>
|
||
<li><strong>MM</strong>: Minute (00-59)</li>
|
||
<li>Optional seconds: <code>HH:MM:SS - HH:MM:SS</code></li>
|
||
</ul>
|
||
<h3 id="examples-2"><a class="header" href="#examples-2">Examples</a></h3>
|
||
<pre><code class="language-storybook">schedule Examples {
|
||
block early { 05:00 - 08:00, action: prep }
|
||
block midday { 12:00 - 13:00, action: lunch }
|
||
block late { 20:00 - 23:59, action: closing }
|
||
}
|
||
</code></pre>
|
||
<h3 id="overnight-ranges"><a class="header" href="#overnight-ranges">Overnight Ranges</a></h3>
|
||
<p>Blocks can span midnight:</p>
|
||
<pre><code class="language-storybook">schedule NightShift {
|
||
block night_work {
|
||
22:00 - 06:00 // 10 PM to 6 AM next day
|
||
action: security::patrol
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>The system interprets this as 22:00-24:00 (day 1) + 00:00-06:00 (day 2).</p>
|
||
<h2 id="actions-1"><a class="header" href="#actions-1">Actions</a></h2>
|
||
<p>The <code>action</code> field links a schedule block to a behavior tree.</p>
|
||
<h3 id="syntax-8"><a class="header" href="#syntax-8">Syntax</a></h3>
|
||
<pre><code class="language-storybook">action: <qualified-path>
|
||
</code></pre>
|
||
<h3 id="examples-3"><a class="header" href="#examples-3">Examples</a></h3>
|
||
<pre><code class="language-storybook">schedule BakerSchedule {
|
||
block morning {
|
||
05:00 - 08:00
|
||
action: baking::prepare_dough
|
||
}
|
||
|
||
block sales {
|
||
08:00 - 14:00
|
||
action: baking::serve_customers
|
||
}
|
||
|
||
block cleanup {
|
||
14:00 - 15:00
|
||
action: baking::close_shop
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="qualified-paths"><a class="header" href="#qualified-paths">Qualified Paths</a></h3>
|
||
<p>Actions can reference behaviors from other modules:</p>
|
||
<pre><code class="language-storybook">schedule GuardSchedule {
|
||
block patrol {
|
||
08:00 - 16:00
|
||
action: behaviors::guards::patrol_route
|
||
}
|
||
|
||
block training {
|
||
16:00 - 18:00
|
||
action: combat::training::drills
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="temporal-constraints-1"><a class="header" href="#temporal-constraints-1">Temporal Constraints</a></h2>
|
||
<p>Temporal constraints specify when a block applies (season, day of week, month, date range).</p>
|
||
<h3 id="season-constraint"><a class="header" href="#season-constraint">Season Constraint</a></h3>
|
||
<pre><code class="language-storybook">schedule SeasonalHours {
|
||
block summer_hours {
|
||
06:00 - 20:00
|
||
action: work::long_day
|
||
on season summer
|
||
}
|
||
|
||
block winter_hours {
|
||
07:00 - 18:00
|
||
action: work::short_day
|
||
on season winter
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Season values</strong> are defined by enums in your storybook:</p>
|
||
<pre><code class="language-storybook">enum Season {
|
||
spring
|
||
summer
|
||
fall
|
||
winter
|
||
}
|
||
</code></pre>
|
||
<h3 id="day-of-week-constraint"><a class="header" href="#day-of-week-constraint">Day of Week Constraint</a></h3>
|
||
<pre><code class="language-storybook">schedule WeeklyPattern {
|
||
block weekday_work {
|
||
09:00 - 17:00
|
||
action: work::standard
|
||
on day monday // Also: tuesday, wednesday, thursday, friday, saturday, sunday
|
||
}
|
||
|
||
block weekend_rest {
|
||
10:00 - 16:00
|
||
action: leisure::relax
|
||
on day saturday
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Day values</strong> are typically defined by a <code>DayOfWeek</code> enum:</p>
|
||
<pre><code class="language-storybook">enum DayOfWeek {
|
||
monday
|
||
tuesday
|
||
wednesday
|
||
thursday
|
||
friday
|
||
saturday
|
||
sunday
|
||
}
|
||
</code></pre>
|
||
<h3 id="month-constraint"><a class="header" href="#month-constraint">Month Constraint</a></h3>
|
||
<pre><code class="language-storybook">schedule AnnualPattern {
|
||
block harvest {
|
||
06:00 - 20:00
|
||
action: farming::harvest_crops
|
||
on month october
|
||
}
|
||
|
||
block spring_planting {
|
||
07:00 - 19:00
|
||
action: farming::plant_seeds
|
||
on month april
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="date-range-constraint"><a class="header" href="#date-range-constraint">Date Range Constraint</a></h3>
|
||
<pre><code class="language-storybook">schedule TouristSeason {
|
||
block high_season {
|
||
08:00 - 22:00
|
||
action: tourism::busy_service
|
||
on dates "Jun 1" .. "Sep 1"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Note</strong>: Date format is implementation-specific. Check your runtime for supported formats.</p>
|
||
<h2 id="recurrence-patterns"><a class="header" href="#recurrence-patterns">Recurrence Patterns</a></h2>
|
||
<p>Recurrence patterns define recurring events that modify the schedule.</p>
|
||
<h3 id="syntax-9"><a class="header" href="#syntax-9">Syntax</a></h3>
|
||
<pre><code class="language-storybook">recurs EventName on <temporal-constraint> {
|
||
<schedule-block>+
|
||
}
|
||
</code></pre>
|
||
<h3 id="examples-4"><a class="header" href="#examples-4">Examples</a></h3>
|
||
<h4 id="weekly-recurring-event"><a class="header" href="#weekly-recurring-event">Weekly Recurring Event</a></h4>
|
||
<pre><code class="language-storybook">schedule MarketSchedule {
|
||
// Regular daily hours
|
||
block work {
|
||
08:00 - 17:00
|
||
action: shop::regular_sales
|
||
}
|
||
|
||
// Market day every saturday
|
||
recurs MarketDay on day saturday {
|
||
block setup {
|
||
06:00 - 08:00
|
||
action: market::setup_stall
|
||
}
|
||
|
||
block busy_market {
|
||
08:00 - 18:00
|
||
action: market::busy_sales
|
||
}
|
||
|
||
block teardown {
|
||
18:00 - 20:00
|
||
action: market::pack_up
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h4 id="monthly-recurring-event"><a class="header" href="#monthly-recurring-event">Monthly Recurring Event</a></h4>
|
||
<pre><code class="language-storybook">schedule TownSchedule {
|
||
recurs CouncilMeeting on month first_monday {
|
||
block meeting {
|
||
18:00 - 21:00
|
||
action: governance::council_session
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h4 id="seasonal-recurring-event"><a class="header" href="#seasonal-recurring-event">Seasonal Recurring Event</a></h4>
|
||
<pre><code class="language-storybook">schedule FarmSchedule {
|
||
recurs SpringPlanting on season spring {
|
||
block planting {
|
||
06:00 - 18:00
|
||
action: farming::plant_all_fields
|
||
}
|
||
}
|
||
|
||
recurs FallHarvest on season fall {
|
||
block harvest {
|
||
06:00 - 20:00
|
||
action: farming::harvest_all_crops
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="schedule-composition"><a class="header" href="#schedule-composition">Schedule Composition</a></h2>
|
||
<p>Schedules can extend other schedules using <code>extends</code>, inheriting and overriding blocks.</p>
|
||
<h3 id="extends-clause"><a class="header" href="#extends-clause">Extends Clause</a></h3>
|
||
<pre><code class="language-storybook">schedule Base {
|
||
block work {
|
||
09:00 - 17:00
|
||
action: work::standard
|
||
}
|
||
}
|
||
|
||
schedule Extended extends Base {
|
||
block work { // Override inherited block
|
||
05:00 - 13:00
|
||
action: work::early_shift
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="override-semantics"><a class="header" href="#override-semantics">Override Semantics</a></h3>
|
||
<p>When extending a schedule:</p>
|
||
<ol>
|
||
<li><strong>Named blocks</strong> with matching names override base blocks</li>
|
||
<li><strong>Unnamed blocks</strong> are appended</li>
|
||
<li><strong>Time range</strong> can change</li>
|
||
<li><strong>Action</strong> can change</li>
|
||
<li><strong>Constraints</strong> can be added/changed</li>
|
||
</ol>
|
||
<h3 id="multiple-levels"><a class="header" href="#multiple-levels">Multiple Levels</a></h3>
|
||
<pre><code class="language-storybook">schedule BaseWork {
|
||
block work {
|
||
09:00 - 17:00
|
||
action: work::standard
|
||
}
|
||
}
|
||
|
||
schedule BakerWork extends BaseWork {
|
||
block work {
|
||
05:00 - 13:00 // Earlier hours
|
||
action: baking::work
|
||
}
|
||
}
|
||
|
||
schedule MasterBaker extends BakerWork {
|
||
block work {
|
||
03:00 - 11:00 // Even earlier!
|
||
action: baking::master_work
|
||
}
|
||
|
||
block teaching {
|
||
14:00 - 16:00
|
||
action: baking::teach_apprentice
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Resolution:</strong></p>
|
||
<ul>
|
||
<li><code>work</code> block: 03:00-11:00 with <code>baking::master_work</code> (MasterBaker overrides BakerWork overrides BaseWork)</li>
|
||
<li><code>teaching</code> block: 14:00-16:00 with <code>baking::teach_apprentice</code> (from MasterBaker)</li>
|
||
</ul>
|
||
<h2 id="complete-examples-2"><a class="header" href="#complete-examples-2">Complete Examples</a></h2>
|
||
<h3 id="simple-daily-schedule"><a class="header" href="#simple-daily-schedule">Simple Daily Schedule</a></h3>
|
||
<pre><code class="language-storybook">schedule SimpleFarmer {
|
||
block sleep {
|
||
22:00 - 05:00
|
||
action: rest::sleep
|
||
}
|
||
|
||
block morning_chores {
|
||
05:00 - 08:00
|
||
action: farming::feed_animals
|
||
}
|
||
|
||
block fieldwork {
|
||
08:00 - 17:00
|
||
action: farming::tend_crops
|
||
}
|
||
|
||
block evening_chores {
|
||
17:00 - 19:00
|
||
action: farming::evening_tasks
|
||
}
|
||
|
||
block leisure {
|
||
19:00 - 22:00
|
||
action: social::family_time
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="seasonal-variation"><a class="header" href="#seasonal-variation">Seasonal Variation</a></h3>
|
||
<pre><code class="language-storybook">schedule SeasonalBaker {
|
||
block summer_work {
|
||
06:00 - 20:00
|
||
action: baking::long_shift
|
||
on season summer
|
||
}
|
||
|
||
block winter_work {
|
||
07:00 - 18:00
|
||
action: baking::short_shift
|
||
on season winter
|
||
}
|
||
|
||
block spring_work {
|
||
06:30 - 19:00
|
||
action: baking::medium_shift
|
||
on season spring
|
||
}
|
||
|
||
block fall_work {
|
||
06:30 - 19:00
|
||
action: baking::medium_shift
|
||
on season fall
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="weekly-pattern-with-recurrence"><a class="header" href="#weekly-pattern-with-recurrence">Weekly Pattern with Recurrence</a></h3>
|
||
<pre><code class="language-storybook">schedule InnkeeperSchedule {
|
||
// Weekday routine
|
||
block weekday_service {
|
||
08:00 - 22:00
|
||
action: inn::regular_service
|
||
on day monday // Repeat for each weekday
|
||
}
|
||
|
||
block weekday_service {
|
||
08:00 - 22:00
|
||
action: inn::regular_service
|
||
on day tuesday
|
||
}
|
||
|
||
// ... (wednesday, thursday, friday)
|
||
|
||
// Weekend hours
|
||
block weekend_service {
|
||
10:00 - 24:00
|
||
action: inn::busy_service
|
||
on day saturday
|
||
}
|
||
|
||
block weekend_service {
|
||
10:00 - 24:00
|
||
action: inn::busy_service
|
||
on day sunday
|
||
}
|
||
|
||
// Weekly market day
|
||
recurs MarketDay on day wednesday {
|
||
block market_prep {
|
||
06:00 - 08:00
|
||
action: inn::prepare_market_goods
|
||
}
|
||
|
||
block market_rush {
|
||
08:00 - 16:00
|
||
action: inn::market_day_chaos
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="extended-schedule"><a class="header" href="#extended-schedule">Extended Schedule</a></h3>
|
||
<pre><code class="language-storybook">schedule BaseShopkeeper {
|
||
block open {
|
||
09:00 - 17:00
|
||
action: shop::standard_hours
|
||
}
|
||
}
|
||
|
||
schedule BlacksmithSchedule extends BaseShopkeeper {
|
||
block open { // Override
|
||
06:00 - 18:00 // Longer hours
|
||
action: smithing::work
|
||
}
|
||
|
||
block forge_heat { // New block
|
||
05:00 - 06:00
|
||
action: smithing::heat_forge
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="complex-year-round-schedule"><a class="header" href="#complex-year-round-schedule">Complex Year-Round Schedule</a></h3>
|
||
<pre><code class="language-storybook">schedule MasterBakerYear {
|
||
// Daily base pattern
|
||
block prep {
|
||
04:00 - 06:00
|
||
action: baking::prepare
|
||
}
|
||
|
||
block baking {
|
||
06:00 - 10:00
|
||
action: baking::bake
|
||
}
|
||
|
||
block sales {
|
||
10:00 - 16:00
|
||
action: baking::serve
|
||
}
|
||
|
||
block cleanup {
|
||
16:00 - 17:00
|
||
action: baking::clean
|
||
}
|
||
|
||
// Seasonal variations
|
||
block summer_hours {
|
||
10:00 - 20:00 // Extended sales
|
||
action: baking::busy_summer
|
||
on season summer
|
||
}
|
||
|
||
// Weekly market
|
||
recurs MarketDay on day saturday {
|
||
block market_prep {
|
||
02:00 - 04:00
|
||
action: baking::market_prep
|
||
}
|
||
|
||
block market_sales {
|
||
08:00 - 18:00
|
||
action: baking::market_rush
|
||
}
|
||
}
|
||
|
||
// Annual events
|
||
recurs HarvestFestival on dates "Sep 20" .. "Sep 25" {
|
||
block festival {
|
||
06:00 - 23:00
|
||
action: baking::festival_mode
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="integration-with-characters-1"><a class="header" href="#integration-with-characters-1">Integration with Characters</a></h2>
|
||
<p>Characters link to schedules using the <code>uses schedule</code> clause:</p>
|
||
<pre><code class="language-storybook">character Martha {
|
||
uses schedule: BakerySchedule
|
||
}
|
||
</code></pre>
|
||
<p><strong>Multiple schedules:</strong></p>
|
||
<pre><code class="language-storybook">character Innkeeper {
|
||
uses schedules: [WeekdaySchedule, WeekendSchedule]
|
||
}
|
||
</code></pre>
|
||
<p>See <a href="reference/./10-characters.html#schedule-integration">Characters</a> for complete integration syntax.</p>
|
||
<h2 id="execution-semantics-2"><a class="header" href="#execution-semantics-2">Execution Semantics</a></h2>
|
||
<h3 id="time-based-selection"><a class="header" href="#time-based-selection">Time-Based Selection</a></h3>
|
||
<p>At any given time, the runtime:</p>
|
||
<ol>
|
||
<li><strong>Evaluates temporal constraints</strong>: Which blocks apply right now?</li>
|
||
<li><strong>Selects matching block</strong>: First block whose time range and constraint match</li>
|
||
<li><strong>Executes action</strong>: Runs the associated behavior tree</li>
|
||
<li><strong>Repeats next tick</strong></li>
|
||
</ol>
|
||
<h3 id="priority-order"><a class="header" href="#priority-order">Priority Order</a></h3>
|
||
<p>When multiple blocks could apply:</p>
|
||
<ol>
|
||
<li><strong>Recurrences</strong> take priority over regular blocks</li>
|
||
<li><strong>Constraints</strong> filter blocks (season, day, month)</li>
|
||
<li><strong>Time ranges</strong> must overlap current time</li>
|
||
<li><strong>First match</strong> wins (declaration order)</li>
|
||
</ol>
|
||
<h3 id="block-overlap"><a class="header" href="#block-overlap">Block Overlap</a></h3>
|
||
<p>Blocks can overlap in time. The runtime selects the first matching block.</p>
|
||
<p><strong>Example:</strong></p>
|
||
<pre><code class="language-storybook">schedule Overlapping {
|
||
block general {
|
||
08:00 - 17:00
|
||
action: work::general
|
||
}
|
||
|
||
block specialized {
|
||
10:00 - 12:00 // Overlaps with general
|
||
action: work::specialized
|
||
on day wednesday
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>On Wednesday at 11:00, <code>specialized</code> block is selected (more specific constraint).</p>
|
||
<h2 id="validation-rules-4"><a class="header" href="#validation-rules-4">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>Time format</strong>: Times must be valid HH:MM or HH:MM:SS (24-hour)</li>
|
||
<li><strong>Time order</strong>: Start time must be before end time (unless spanning midnight)</li>
|
||
<li><strong>Extends exists</strong>: If using <code>extends</code>, base schedule must be defined</li>
|
||
<li><strong>No circular extends</strong>: Cannot form circular dependency chains</li>
|
||
<li><strong>Named blocks unique</strong>: Block names must be unique within a schedule</li>
|
||
<li><strong>Action exists</strong>: Action references must resolve to defined behaviors</li>
|
||
<li><strong>Constraint values</strong>: Temporal constraint values must reference defined enums</li>
|
||
<li><strong>Recurrence names unique</strong>: Recurrence names must be unique within a schedule</li>
|
||
</ol>
|
||
<h2 id="best-practices-3"><a class="header" href="#best-practices-3">Best Practices</a></h2>
|
||
<h3 id="1-use-named-blocks-for-overrides"><a class="header" href="#1-use-named-blocks-for-overrides">1. Use Named Blocks for Overrides</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">schedule Extended extends Base {
|
||
block { 05:00 - 13:00, action: work } // Appends instead of overriding
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">schedule Extended extends Base {
|
||
block work { 05:00 - 13:00, action: early_work } // Overrides by name
|
||
}
|
||
</code></pre>
|
||
<h3 id="2-group-related-blocks"><a class="header" href="#2-group-related-blocks">2. Group Related Blocks</a></h3>
|
||
<pre><code class="language-storybook">schedule DailyRoutine {
|
||
// Sleep blocks
|
||
block night_sleep { 22:00 - 05:00, action: sleep }
|
||
|
||
// Morning blocks
|
||
block wake_up { 05:00 - 06:00, action: morning_routine }
|
||
block breakfast { 06:00 - 07:00, action: eat_breakfast }
|
||
|
||
// Work blocks
|
||
block commute { 07:00 - 08:00, action: travel_to_work }
|
||
block work { 08:00 - 17:00, action: work }
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-use-recurrences-for-special-events"><a class="header" href="#3-use-recurrences-for-special-events">3. Use Recurrences for Special Events</a></h3>
|
||
<p><strong>Avoid:</strong> Duplicating blocks manually</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">recurs MarketDay on day saturday {
|
||
block market { 08:00 - 16:00, action: market_work }
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-extend-for-variations"><a class="header" href="#4-extend-for-variations">4. Extend for Variations</a></h3>
|
||
<p><strong>Avoid:</strong> Duplicating entire schedules</p>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">schedule WorkerBase { ... }
|
||
schedule EarlyWorker extends WorkerBase { ... }
|
||
schedule NightWorker extends WorkerBase { ... }
|
||
</code></pre>
|
||
<h3 id="5-use-temporal-constraints-for-clarity"><a class="header" href="#5-use-temporal-constraints-for-clarity">5. Use Temporal Constraints for Clarity</a></h3>
|
||
<pre><code class="language-storybook">block summer_hours {
|
||
06:00 - 20:00
|
||
action: long_shift
|
||
on season summer // Explicit and readable
|
||
}
|
||
</code></pre>
|
||
<h2 id="cross-references-4"><a class="header" href="#cross-references-4">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> - Linking schedules to characters</li>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> - Actions reference behavior trees</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - Time and duration literals</li>
|
||
<li><a href="reference/../reference/16-other-declarations.html#enums">Enums</a> - Defining seasons, days, months</li>
|
||
</ul>
|
||
<h2 id="related-concepts-4"><a class="header" href="#related-concepts-4">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Time-based AI</strong>: Schedules drive time-dependent behavior</li>
|
||
<li><strong>Template inheritance</strong>: <code>extends</code> enables schedule reuse</li>
|
||
<li><strong>Temporal constraints</strong>: Filter blocks by season, day, month</li>
|
||
<li><strong>Recurrence patterns</strong>: Define repeating events</li>
|
||
<li><strong>Composition</strong>: Build complex schedules from simple parts</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="relationships"><a class="header" href="#relationships">Relationships</a></h1>
|
||
<p>Relationships define connections between characters, institutions, and other entities. They capture social bonds, power dynamics, emotional ties, and interactive patterns. Relationships can be symmetric (friendship) or asymmetric (parent-child), and support bidirectional perspectives where each participant has different fields.</p>
|
||
<h2 id="what-is-a-relationship"><a class="header" href="#what-is-a-relationship">What is a Relationship?</a></h2>
|
||
<p>A relationship connects two or more participants and describes:</p>
|
||
<ul>
|
||
<li><strong>Participants</strong>: The entities involved (characters, institutions)</li>
|
||
<li><strong>Roles</strong>: Optional labels (parent, employer, spouse)</li>
|
||
<li><strong>Shared fields</strong>: Properties of the relationship itself (bond strength, duration)</li>
|
||
<li><strong>Perspective fields</strong>: How each participant views the relationship (<code>self</code>/<code>other</code> blocks)</li>
|
||
</ul>
|
||
<pre><code>Relationship: ParentChild
|
||
├─ Participant: Martha (as parent)
|
||
│ ├─ self: { responsibility: 1.0, protective: 0.9 }
|
||
│ └─ other: { dependent: 0.8 }
|
||
├─ Participant: Tommy (as child)
|
||
└─ Shared: { bond: 0.9, years_together: 8 }
|
||
</code></pre>
|
||
<h2 id="syntax-10"><a class="header" href="#syntax-10">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><relationship-decl> ::= "relationship" <identifier> <body>
|
||
|
||
<body> ::= "{" <participant>+ <field>* "}"
|
||
|
||
<participant> ::= <qualified-path> <role-clause>? <perspective-blocks>?
|
||
|
||
<role-clause> ::= "as" <identifier>
|
||
|
||
<perspective-blocks> ::= "self" "{" <field>* "}" "other" "{" <field>* "}"
|
||
| "self" "{" <field>* "}"
|
||
| "other" "{" <field>* "}"
|
||
|
||
<field> ::= <identifier> ":" <value>
|
||
</code></pre>
|
||
<h2 id="basic-relationships-1"><a class="header" href="#basic-relationships-1">Basic Relationships</a></h2>
|
||
<h3 id="simple-two-party-relationship"><a class="header" href="#simple-two-party-relationship">Simple Two-Party Relationship</a></h3>
|
||
<pre><code class="language-storybook">relationship Friendship {
|
||
Martha
|
||
Gregory
|
||
bond: 0.8
|
||
years_known: 15
|
||
}
|
||
</code></pre>
|
||
<p><strong>Semantics:</strong></p>
|
||
<ul>
|
||
<li>Two participants: Martha and Gregory</li>
|
||
<li>Shared field: <code>bond</code> (strength of friendship)</li>
|
||
<li>Shared field: <code>years_known</code> (duration)</li>
|
||
</ul>
|
||
<h3 id="multi-party-relationship"><a class="header" href="#multi-party-relationship">Multi-Party Relationship</a></h3>
|
||
<pre><code class="language-storybook">relationship Family {
|
||
Martha
|
||
David
|
||
Tommy
|
||
Elena
|
||
|
||
household: "Baker Residence"
|
||
family_bond: 0.95
|
||
}
|
||
</code></pre>
|
||
<p>Relationships can have any number of participants.</p>
|
||
<h2 id="roles"><a class="header" href="#roles">Roles</a></h2>
|
||
<p>Roles label a participant’s function in the relationship.</p>
|
||
<h3 id="syntax-11"><a class="header" href="#syntax-11">Syntax</a></h3>
|
||
<pre><code class="language-storybook">ParticipantName as role_name
|
||
</code></pre>
|
||
<h3 id="examples-5"><a class="header" href="#examples-5">Examples</a></h3>
|
||
<pre><code class="language-storybook">relationship Marriage {
|
||
Martha as spouse
|
||
David as spouse
|
||
|
||
bond: 0.9
|
||
anniversary: "2015-06-20"
|
||
}
|
||
</code></pre>
|
||
<pre><code class="language-storybook">relationship ParentChild {
|
||
Martha as parent
|
||
Tommy as child
|
||
|
||
bond: 0.95
|
||
guardianship: true
|
||
}
|
||
</code></pre>
|
||
<pre><code class="language-storybook">relationship EmployerEmployee {
|
||
Martha as employer
|
||
Elena as employee
|
||
|
||
workplace: "Martha's Bakery"
|
||
salary: 45000
|
||
}
|
||
</code></pre>
|
||
<h3 id="role-semantics"><a class="header" href="#role-semantics">Role Semantics</a></h3>
|
||
<ul>
|
||
<li><strong>Labels</strong>: Roles are descriptive labels, not types</li>
|
||
<li><strong>Multiple roles</strong>: Same person can have different roles in different relationships</li>
|
||
<li><strong>Optional</strong>: Roles are optional—participants can be unnamed</li>
|
||
<li><strong>Flexibility</strong>: No predefined role vocabulary—use what makes sense</li>
|
||
</ul>
|
||
<h2 id="perspective-fields-selfother-blocks"><a class="header" href="#perspective-fields-selfother-blocks">Perspective Fields (Self/Other Blocks)</a></h2>
|
||
<p>Perspective fields specify how each participant views the relationship. They enable asymmetric, bidirectional relationships.</p>
|
||
<h3 id="syntax-12"><a class="header" href="#syntax-12">Syntax</a></h3>
|
||
<pre><code class="language-storybook">ParticipantName as role self {
|
||
// Fields describing this participant's perspective
|
||
} other {
|
||
// Fields describing how this participant views others
|
||
}
|
||
</code></pre>
|
||
<h3 id="self-block"><a class="header" href="#self-block">Self Block</a></h3>
|
||
<p>The <code>self</code> block contains fields about how the participant experiences the relationship:</p>
|
||
<pre><code class="language-storybook">relationship ParentChild {
|
||
Martha as parent self {
|
||
responsibility: 1.0
|
||
protective: 0.9
|
||
anxiety_level: 0.6
|
||
}
|
||
Tommy as child
|
||
}
|
||
</code></pre>
|
||
<p><strong>Martha’s perspective:</strong></p>
|
||
<ul>
|
||
<li><code>responsibility</code>: She feels 100% responsible</li>
|
||
<li><code>protective</code>: She’s highly protective (90%)</li>
|
||
<li><code>anxiety_level</code>: Moderate anxiety about parenting</li>
|
||
</ul>
|
||
<h3 id="other-block"><a class="header" href="#other-block">Other Block</a></h3>
|
||
<p>The <code>other</code> block contains fields about how the participant views the other participants:</p>
|
||
<pre><code class="language-storybook">relationship ParentChild {
|
||
Martha as parent self {
|
||
responsibility: 1.0
|
||
} other {
|
||
dependent: 0.8 // She sees Tommy as 80% dependent
|
||
mature_for_age: 0.7 // She thinks he's fairly mature
|
||
}
|
||
Tommy as child
|
||
}
|
||
</code></pre>
|
||
<h3 id="both-blocks"><a class="header" href="#both-blocks">Both Blocks</a></h3>
|
||
<pre><code class="language-storybook">relationship EmployerEmployee {
|
||
Martha as employer self {
|
||
authority: 0.9
|
||
stress: 0.6
|
||
} other {
|
||
respect: 0.8
|
||
trust: 0.85
|
||
}
|
||
Elena as employee
|
||
}
|
||
</code></pre>
|
||
<p><strong>Martha’s perspective:</strong></p>
|
||
<ul>
|
||
<li><strong>Self</strong>: She has high authority (90%), moderate stress (60%)</li>
|
||
<li><strong>Other</strong>: She respects Elena (80%), trusts her (85%)</li>
|
||
</ul>
|
||
<h3 id="asymmetric-relationships"><a class="header" href="#asymmetric-relationships">Asymmetric Relationships</a></h3>
|
||
<p>Different participants can have different perspective fields:</p>
|
||
<pre><code class="language-storybook">relationship TeacherStudent {
|
||
Gandalf as teacher self {
|
||
patience: 0.8
|
||
wisdom_to_impart: 1.0
|
||
} other {
|
||
potential: 0.9
|
||
ready_to_learn: 0.6
|
||
}
|
||
|
||
Frodo as student self {
|
||
eager_to_learn: 0.7
|
||
overwhelmed: 0.5
|
||
} other {
|
||
admiration: 0.95
|
||
intimidated: 0.4
|
||
}
|
||
|
||
bond: 0.85
|
||
}
|
||
</code></pre>
|
||
<p><strong>Gandalf’s view:</strong></p>
|
||
<ul>
|
||
<li>He’s patient (80%), has much wisdom to share</li>
|
||
<li>Sees Frodo as having high potential but only moderately ready</li>
|
||
</ul>
|
||
<p><strong>Frodo’s view:</strong></p>
|
||
<ul>
|
||
<li>He’s eager but overwhelmed</li>
|
||
<li>Deeply admires Gandalf, slightly intimidated</li>
|
||
</ul>
|
||
<h2 id="shared-fields"><a class="header" href="#shared-fields">Shared Fields</a></h2>
|
||
<p>Fields declared at the relationship level (not in <code>self</code>/<code>other</code> blocks) are <strong>shared</strong> among all participants.</p>
|
||
<pre><code class="language-storybook">relationship Friendship {
|
||
Martha
|
||
Gregory
|
||
|
||
bond: 0.8 // Shared: mutual bond strength
|
||
years_known: 15 // Shared: how long they've known each other
|
||
shared_routines: 3 // Shared: number of daily routines
|
||
}
|
||
</code></pre>
|
||
<p><strong>Common shared fields:</strong></p>
|
||
<ul>
|
||
<li><code>bond</code>: Relationship strength (0.0 to 1.0)</li>
|
||
<li><code>years_known</code>: Duration of relationship</li>
|
||
<li><code>trust</code>: Mutual trust level</li>
|
||
<li><code>commitment</code>: Commitment to the relationship</li>
|
||
<li><code>compatibility</code>: How well they get along</li>
|
||
</ul>
|
||
<h2 id="prose-blocks-3"><a class="header" href="#prose-blocks-3">Prose Blocks</a></h2>
|
||
<p>Relationships can include prose blocks for narrative context.</p>
|
||
<pre><code class="language-storybook">relationship MarthaAndGregory {
|
||
Martha {
|
||
role: shopkeeper
|
||
values_loyalty: 0.9
|
||
|
||
---perspective
|
||
Martha appreciates Gregory's unwavering loyalty. He has
|
||
been buying her sourdough loaf every morning for fifteen
|
||
years. Their brief daily exchanges about the weather and
|
||
local gossip are a comforting routine.
|
||
---
|
||
}
|
||
|
||
Gregory {
|
||
role: regular_customer
|
||
always_orders: "sourdough_loaf"
|
||
|
||
---perspective
|
||
Gregory considers Martha's bakery a cornerstone of his
|
||
daily routine. The bread is excellent, but it is the brief
|
||
human connection that keeps him coming back.
|
||
---
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Common prose tags:</strong></p>
|
||
<ul>
|
||
<li><code>---perspective</code>: How a participant views the relationship</li>
|
||
<li><code>---history</code>: Background of the relationship</li>
|
||
<li><code>---dynamics</code>: How the relationship functions</li>
|
||
<li><code>---notes</code>: Design or development notes</li>
|
||
</ul>
|
||
<p>Note: In this syntax, perspective fields are inside participant blocks (not separate <code>self</code>/<code>other</code> blocks).</p>
|
||
<h2 id="complete-examples-3"><a class="header" href="#complete-examples-3">Complete Examples</a></h2>
|
||
<h3 id="simple-friendship"><a class="header" href="#simple-friendship">Simple Friendship</a></h3>
|
||
<pre><code class="language-storybook">relationship Friendship {
|
||
Martha
|
||
NeighborBaker
|
||
|
||
bond: 0.6
|
||
years_known: 10
|
||
}
|
||
</code></pre>
|
||
<h3 id="marriage-with-roles"><a class="header" href="#marriage-with-roles">Marriage with Roles</a></h3>
|
||
<pre><code class="language-storybook">relationship Marriage {
|
||
Martha as spouse
|
||
David as spouse
|
||
|
||
bond: 0.9
|
||
anniversary: "2015-06-20"
|
||
children: 2
|
||
}
|
||
</code></pre>
|
||
<h3 id="parent-child-with-perspectives"><a class="header" href="#parent-child-with-perspectives">Parent-Child with Perspectives</a></h3>
|
||
<pre><code class="language-storybook">relationship ParentChild {
|
||
Martha as parent self {
|
||
responsibility: 1.0
|
||
protective: 0.9
|
||
anxiety_level: 0.6
|
||
} other {
|
||
dependent: 0.8
|
||
mature_for_age: 0.7
|
||
}
|
||
|
||
Tommy as child self {
|
||
seeks_independence: 0.7
|
||
appreciates_parent: 0.9
|
||
} other {
|
||
feels_understood: 0.6
|
||
wants_more_freedom: 0.8
|
||
}
|
||
|
||
bond: 0.95
|
||
years_together: 8
|
||
}
|
||
</code></pre>
|
||
<h3 id="employer-employee"><a class="header" href="#employer-employee">Employer-Employee</a></h3>
|
||
<pre><code class="language-storybook">relationship EmployerEmployee {
|
||
Martha as employer self {
|
||
authority: 0.9
|
||
satisfaction_with_employee: 0.85
|
||
} other {
|
||
respect: 0.8
|
||
trust: 0.85
|
||
}
|
||
|
||
Elena as employee self {
|
||
job_satisfaction: 0.8
|
||
career_growth: 0.7
|
||
} other {
|
||
respects_boss: 0.9
|
||
appreciates_flexibility: 0.95
|
||
}
|
||
|
||
workplace: "Martha's Bakery"
|
||
salary: 45000
|
||
employment_start: "2023-01-15"
|
||
}
|
||
</code></pre>
|
||
<h3 id="complex-multi-perspective"><a class="header" href="#complex-multi-perspective">Complex Multi-Perspective</a></h3>
|
||
<pre><code class="language-storybook">relationship MentorApprentice {
|
||
Martha {
|
||
role: mentor
|
||
teaching_style: "patient"
|
||
investment: 0.9
|
||
|
||
---perspective
|
||
Martha sees Elena as the daughter she might have had in
|
||
the trade. She recognizes the same passion she felt at
|
||
that age and pushes Elena harder because she knows the
|
||
talent is there. Every correction comes from love.
|
||
---
|
||
}
|
||
|
||
Elena {
|
||
role: apprentice
|
||
dedication: 0.9
|
||
anxiety: 0.4
|
||
|
||
---perspective
|
||
Elena idolizes Martha's skill but fears disappointing
|
||
her. Every morning she arrives thirty minutes early to
|
||
practice techniques before Martha gets in. She keeps a
|
||
notebook of every correction, reviewing them each night.
|
||
|
||
"I want to be half as good as her someday" -- this quiet
|
||
ambition drives everything Elena does.
|
||
---
|
||
}
|
||
|
||
bond: 0.85
|
||
}
|
||
</code></pre>
|
||
<h3 id="business-rivalry"><a class="header" href="#business-rivalry">Business Rivalry</a></h3>
|
||
<pre><code class="language-storybook">relationship BakeryRivalry {
|
||
Martha {
|
||
role: established_baker
|
||
aware_of_competition: true
|
||
respects_rival: 0.6
|
||
|
||
---perspective
|
||
Martha views the new bakery across town as healthy
|
||
competition. She respects their pastry work but knows
|
||
her sourdough is unmatched. The rivalry pushes her to
|
||
keep innovating.
|
||
---
|
||
}
|
||
|
||
RivalBaker {
|
||
role: newcomer
|
||
wants_to_surpass: true
|
||
studies_martha: 0.8
|
||
|
||
---perspective
|
||
The rival baker moved to town specifically because of
|
||
Martha's reputation. They study her techniques, buy her
|
||
bread to analyze it, and dream of the day a customer
|
||
chooses their loaf over Martha's sourdough.
|
||
---
|
||
}
|
||
|
||
bond: 0.3
|
||
}
|
||
</code></pre>
|
||
<h3 id="multi-party-family"><a class="header" href="#multi-party-family">Multi-Party Family</a></h3>
|
||
<pre><code class="language-storybook">relationship BakerFamily {
|
||
Martha as parent
|
||
David as parent
|
||
Tommy as child
|
||
Elena as child
|
||
|
||
household: "Baker Residence"
|
||
family_bond: 0.95
|
||
dinner_time: 18:00
|
||
|
||
---dynamics
|
||
A loving queer family running a bakery together. Martha and
|
||
David met at culinary school, married, and adopted Tommy and
|
||
Elena. The whole family works at the bakery on weekends.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="co-owners-partnership"><a class="header" href="#co-owners-partnership">Co-Owners Partnership</a></h3>
|
||
<pre><code class="language-storybook">relationship BakeryPartnership {
|
||
Martha {
|
||
role: co_owner
|
||
specialty: "bread"
|
||
handles_finances: true
|
||
|
||
---perspective
|
||
Martha and Jane complement each other perfectly. Martha
|
||
handles the bread and business side while Jane creates
|
||
the pastries that draw customers in. Together they have
|
||
built something neither could alone.
|
||
---
|
||
}
|
||
|
||
Jane {
|
||
role: co_owner
|
||
specialty: "pastries"
|
||
handles_creativity: true
|
||
|
||
---perspective
|
||
Jane considers Martha the steady foundation of their
|
||
partnership. While Jane experiments and creates, Martha
|
||
ensures the bakery runs like clockwork. Their different
|
||
strengths make the bakery stronger.
|
||
---
|
||
}
|
||
|
||
bond: 0.9
|
||
}
|
||
</code></pre>
|
||
<h2 id="participant-types"><a class="header" href="#participant-types">Participant Types</a></h2>
|
||
<p>Participants can be:</p>
|
||
<ol>
|
||
<li><strong>Characters</strong>: Most common</li>
|
||
<li><strong>Institutions</strong>: Organizations in relationships</li>
|
||
<li><strong>Locations</strong>: Less common, but valid (e.g., “GuardianOfPlace”)</li>
|
||
</ol>
|
||
<pre><code class="language-storybook">relationship GuildMembership {
|
||
Elena as member
|
||
BakersGuild as organization
|
||
|
||
membership_since: "2023-01-01"
|
||
standing: "good"
|
||
dues_paid: true
|
||
}
|
||
</code></pre>
|
||
<h2 id="field-resolution-1"><a class="header" href="#field-resolution-1">Field Resolution</a></h2>
|
||
<p>When a relationship is resolved, fields are merged:</p>
|
||
<ol>
|
||
<li><strong>Relationship shared fields</strong> apply to all participants</li>
|
||
<li><strong>Participant <code>self</code> blocks</strong> apply to that participant</li>
|
||
<li><strong>Participant <code>other</code> blocks</strong> describe how that participant views others</li>
|
||
</ol>
|
||
<p><strong>Example:</strong></p>
|
||
<pre><code class="language-storybook">relationship Example {
|
||
Martha as friend self {
|
||
loyalty: 0.9
|
||
} other {
|
||
trust: 0.8
|
||
}
|
||
Gregory as friend
|
||
|
||
bond: 0.85
|
||
}
|
||
</code></pre>
|
||
<p><strong>Resolution:</strong></p>
|
||
<ul>
|
||
<li>Martha gets: <code>loyalty: 0.9</code> (from self), <code>trust: 0.8</code> (towards Gregory, from other), <code>bond: 0.85</code> (shared)</li>
|
||
<li>Gregory gets: <code>bond: 0.85</code> (shared)</li>
|
||
<li>Relationship gets: <code>bond: 0.85</code></li>
|
||
</ul>
|
||
<h2 id="validation-rules-5"><a class="header" href="#validation-rules-5">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>At least two participants</strong>: Relationships require ≥2 participants</li>
|
||
<li><strong>Participants exist</strong>: All participant names must reference defined entities</li>
|
||
<li><strong>Unique participant names</strong>: Each participant appears at most once</li>
|
||
<li><strong>Field type consistency</strong>: Fields must have valid value types</li>
|
||
<li><strong>No circular relationships</strong>: Avoid infinite relationship chains (warning)</li>
|
||
<li><strong>Self/other completeness</strong>: If using <code>self</code>/<code>other</code>, both blocks should be present (best practice)</li>
|
||
</ol>
|
||
<h2 id="use-cases-1"><a class="header" href="#use-cases-1">Use Cases</a></h2>
|
||
<h3 id="social-networks"><a class="header" href="#social-networks">Social Networks</a></h3>
|
||
<pre><code class="language-storybook">relationship BakerFriendship {
|
||
Martha
|
||
Gregory
|
||
bond: 0.8
|
||
}
|
||
|
||
relationship SupplierPartnership {
|
||
Martha
|
||
Farmer_Jenkins
|
||
bond: 0.7
|
||
}
|
||
|
||
relationship BakeryRivalry {
|
||
Martha
|
||
RivalBaker
|
||
bond: 0.2
|
||
competitive: true
|
||
}
|
||
</code></pre>
|
||
<h3 id="family-structures"><a class="header" href="#family-structures">Family Structures</a></h3>
|
||
<pre><code class="language-storybook">relationship ParentChild {
|
||
Martha as parent
|
||
Tommy as child
|
||
bond: 0.95
|
||
}
|
||
|
||
relationship Siblings {
|
||
Tommy as older_sibling
|
||
Elena as younger_sibling
|
||
bond: 0.85
|
||
rivalry: 0.3
|
||
}
|
||
</code></pre>
|
||
<h3 id="power-dynamics"><a class="header" href="#power-dynamics">Power Dynamics</a></h3>
|
||
<pre><code class="language-storybook">relationship Vassalage {
|
||
King as lord self {
|
||
grants: "protection"
|
||
expects: "loyalty"
|
||
} other {
|
||
trusts: 0.6
|
||
}
|
||
|
||
Knight as vassal self {
|
||
swears: "fealty"
|
||
expects: "land"
|
||
} other {
|
||
respects: 0.9
|
||
}
|
||
|
||
oath_date: "1205-03-15"
|
||
}
|
||
</code></pre>
|
||
<h3 id="asymmetric-awareness-1"><a class="header" href="#asymmetric-awareness-1">Asymmetric Awareness</a></h3>
|
||
<pre><code class="language-storybook">relationship StalkingVictim {
|
||
Stalker as pursuer self {
|
||
obsession: 0.95
|
||
distance_maintained: 50 // meters
|
||
} other {
|
||
believes_unnoticed: true
|
||
}
|
||
|
||
Victim as unaware_target self {
|
||
awareness: 0.0
|
||
}
|
||
|
||
danger_level: 0.8
|
||
}
|
||
</code></pre>
|
||
<h2 id="best-practices-4"><a class="header" href="#best-practices-4">Best Practices</a></h2>
|
||
<h3 id="1-use-descriptive-relationship-names"><a class="header" href="#1-use-descriptive-relationship-names">1. Use Descriptive Relationship Names</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">relationship R1 { ... }
|
||
relationship MarthaGregory { ... }
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">relationship Friendship { ... }
|
||
relationship ParentChild { ... }
|
||
relationship MentorApprentice { ... }
|
||
</code></pre>
|
||
<h3 id="2-use-roles-for-clarity"><a class="header" href="#2-use-roles-for-clarity">2. Use Roles for Clarity</a></h3>
|
||
<pre><code class="language-storybook">relationship Marriage {
|
||
Martha as spouse
|
||
David as spouse
|
||
}
|
||
</code></pre>
|
||
<p>Better than:</p>
|
||
<pre><code class="language-storybook">relationship Marriage {
|
||
Martha
|
||
David
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-shared-fields-for-mutual-properties"><a class="header" href="#3-shared-fields-for-mutual-properties">3. Shared Fields for Mutual Properties</a></h3>
|
||
<pre><code class="language-storybook">relationship Partnership {
|
||
Martha
|
||
Jane
|
||
|
||
bond: 0.9 // Mutual bond
|
||
years_together: 5 // Shared history
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-selfother-for-perspectives"><a class="header" href="#4-selfother-for-perspectives">4. Self/Other for Perspectives</a></h3>
|
||
<pre><code class="language-storybook">relationship TeacherStudent {
|
||
Teacher as mentor self {
|
||
patience: 0.8
|
||
} other {
|
||
potential: 0.9
|
||
}
|
||
|
||
Student as learner self {
|
||
motivation: 0.7
|
||
} other {
|
||
admiration: 0.95
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="5-prose-blocks-for-rich-context"><a class="header" href="#5-prose-blocks-for-rich-context">5. Prose Blocks for Rich Context</a></h3>
|
||
<pre><code class="language-storybook">relationship ComplexDynamic {
|
||
Martha { ... }
|
||
Jane { ... }
|
||
|
||
---dynamics
|
||
Their relationship is characterized by mutual respect but
|
||
divergent goals. Martha focuses on traditional bread while Jane
|
||
pushes for experimental pastries, creating creative tension.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="cross-references-5"><a class="header" href="#cross-references-5">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> - Participants are typically characters</li>
|
||
<li><a href="reference/./16-other-declarations.html#institutions">Institutions</a> - Institutions can participate</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - Field value types</li>
|
||
<li><a href="reference/./17-expressions.html">Expression Language</a> - Querying relationships</li>
|
||
</ul>
|
||
<h2 id="related-concepts-5"><a class="header" href="#related-concepts-5">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Bidirectional modeling</strong>: <code>self</code>/<code>other</code> blocks enable asymmetric perspectives</li>
|
||
<li><strong>Social simulation</strong>: Relationships drive character interactions</li>
|
||
<li><strong>Narrative depth</strong>: Prose blocks embed storytelling in relationship data</li>
|
||
<li><strong>Power dynamics</strong>: Roles and perspective fields model hierarchies</li>
|
||
<li><strong>Emotional bonds</strong>: Bond strength and trust metrics quantify connections</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="locations"><a class="header" href="#locations">Locations</a></h1>
|
||
<p>Locations define places in your world – rooms, buildings, cities, landscapes, or abstract spaces. They provide spatial context for characters, events, and narratives.</p>
|
||
<hr />
|
||
<h2 id="syntax-13"><a class="header" href="#syntax-13">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><location-decl> ::= "location" <identifier> "{" <field>* <prose-block>* "}"
|
||
</code></pre>
|
||
<p>A location declaration consists of:</p>
|
||
<ul>
|
||
<li>The <code>location</code> keyword</li>
|
||
<li>A unique name (identifier)</li>
|
||
<li>A body block containing fields and optional prose blocks</li>
|
||
</ul>
|
||
<p>Locations are one of the simpler declaration types – they hold fields and prose blocks but do not support resource linking (<code>uses behaviors</code> / <code>uses schedule</code>) like characters or institutions.</p>
|
||
<hr />
|
||
<h2 id="basic-location"><a class="header" href="#basic-location">Basic Location</a></h2>
|
||
<p>The simplest location has a name and descriptive fields:</p>
|
||
<pre><code class="language-storybook">location BakersBakery {
|
||
type: bakery
|
||
capacity: 30
|
||
address: "14 Main Street"
|
||
open_hours: "06:00-18:00"
|
||
}
|
||
</code></pre>
|
||
<p>Fields can use any <a href="reference/./18-value-types.html">value type</a>: integers, floats, strings, booleans, enums, lists, ranges, and durations.</p>
|
||
<hr />
|
||
<h2 id="fields-1"><a class="header" href="#fields-1">Fields</a></h2>
|
||
<p>Location fields describe properties of the place:</p>
|
||
<pre><code class="language-storybook">location BakerHome {
|
||
type: residence
|
||
bedrooms: 3
|
||
has_garden: true
|
||
garden_size_sqft: 450.0
|
||
residents: ["Martha", "David", "Jane"]
|
||
comfort_level: 0.85
|
||
}
|
||
</code></pre>
|
||
<h3 id="common-field-patterns"><a class="header" href="#common-field-patterns">Common Field Patterns</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody>
|
||
<tr><td><code>type</code></td><td>enum/identifier</td><td>Category of the place</td></tr>
|
||
<tr><td><code>capacity</code></td><td>integer</td><td>How many can be in this place</td></tr>
|
||
<tr><td><code>size</code></td><td>enum/float</td><td>Physical size</td></tr>
|
||
<tr><td><code>coordinates</code></td><td>integer/float</td><td>Position in a world map</td></tr>
|
||
<tr><td><code>accessible</code></td><td>boolean</td><td>Whether characters can enter</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>These are conventions, not enforced schema. You define whatever fields are meaningful for your world.</p>
|
||
<hr />
|
||
<h2 id="prose-blocks-4"><a class="header" href="#prose-blocks-4">Prose Blocks</a></h2>
|
||
<p>Locations support prose blocks for rich narrative content. Prose blocks are delimited by <code>---tag</code> markers:</p>
|
||
<pre><code class="language-storybook">location BakersBakery {
|
||
type: bakery
|
||
capacity: 30
|
||
|
||
---description
|
||
A warm, inviting bakery on Main Street. The smell of fresh bread
|
||
wafts out the door every morning at dawn. Martha has run the shop
|
||
for fifteen years, and the locals consider it the heart of the
|
||
neighborhood.
|
||
---
|
||
|
||
---atmosphere
|
||
Flour dust catches the light from tall windows. A display case
|
||
holds rows of golden pastries. Behind the counter, the kitchen
|
||
hums with activity from 4 AM onward.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prose block rules:</strong></p>
|
||
<ul>
|
||
<li>Start with <code>---tag_name</code> on its own line</li>
|
||
<li>Content is free-form text (Markdown supported)</li>
|
||
<li>End with <code>---</code> on its own line</li>
|
||
<li>The tag name becomes the key for retrieval</li>
|
||
<li>Multiple prose blocks per location are allowed</li>
|
||
<li>Each tag must be unique within the location</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="ranges"><a class="header" href="#ranges">Ranges</a></h2>
|
||
<p>Locations can use range values for procedural variation:</p>
|
||
<pre><code class="language-storybook">location MarketSquare {
|
||
type: outdoor_market
|
||
stalls: 10..25
|
||
daily_visitors: 50..200
|
||
noise_level: 0.4..0.9
|
||
}
|
||
</code></pre>
|
||
<p>When instantiated, values are selected from within the specified range. This is useful for locations that should vary across simulation runs.</p>
|
||
<hr />
|
||
<h2 id="lists"><a class="header" href="#lists">Lists</a></h2>
|
||
<p>Locations can hold list values:</p>
|
||
<pre><code class="language-storybook">location BakersBakery {
|
||
type: bakery
|
||
products: ["sourdough", "croissants", "rye bread", "cinnamon rolls"]
|
||
equipment: ["stone oven", "mixing station", "proofing cabinet"]
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="referencing-other-entities"><a class="header" href="#referencing-other-entities">Referencing Other Entities</a></h2>
|
||
<p>Location fields can reference other declarations by name:</p>
|
||
<pre><code class="language-storybook">location BakersBakery {
|
||
type: bakery
|
||
owner: Martha
|
||
neighborhood: MainStreet
|
||
part_of: TownCenter
|
||
}
|
||
</code></pre>
|
||
<p>These are identifier references – they are not validated as cross-references at parse time, but the resolver checks that referenced names exist in the name table.</p>
|
||
<hr />
|
||
<h2 id="nested-structure-with-fields"><a class="header" href="#nested-structure-with-fields">Nested Structure with Fields</a></h2>
|
||
<p>You can model spatial hierarchy using fields:</p>
|
||
<pre><code class="language-storybook">location BakersBakery {
|
||
type: bakery
|
||
parent: MainStreet
|
||
|
||
// Zones within the bakery
|
||
has_kitchen: true
|
||
has_storefront: true
|
||
has_storage_room: true
|
||
|
||
// Dimensions
|
||
total_sqft: 1200
|
||
kitchen_sqft: 600
|
||
storefront_sqft: 400
|
||
storage_sqft: 200
|
||
}
|
||
|
||
location MainStreet {
|
||
type: street
|
||
parent: TownCenter
|
||
length_meters: 500
|
||
shops: 12
|
||
}
|
||
|
||
location TownCenter {
|
||
type: district
|
||
population: 2000
|
||
}
|
||
</code></pre>
|
||
<p>There is no built-in parent-child relationship for locations – you model hierarchy through conventional field names like <code>part_of</code>, <code>parent</code>, or <code>contains</code>.</p>
|
||
<hr />
|
||
<h2 id="location-with-enum-fields"><a class="header" href="#location-with-enum-fields">Location with Enum Fields</a></h2>
|
||
<p>Use <a href="reference/./16-other-declarations.html#enums">enums</a> for type-safe categorization:</p>
|
||
<pre><code class="language-storybook">enum PlaceType {
|
||
residence, shop, workshop, office,
|
||
park, street, square, church
|
||
}
|
||
|
||
enum Accessibility {
|
||
public, private, restricted, members_only
|
||
}
|
||
|
||
location BakersBakery {
|
||
type: shop
|
||
accessibility: public
|
||
capacity: 30
|
||
}
|
||
|
||
location BakerHome {
|
||
type: residence
|
||
accessibility: private
|
||
capacity: 8
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="complete-example-baker-family-locations"><a class="header" href="#complete-example-baker-family-locations">Complete Example: Baker Family Locations</a></h2>
|
||
<pre><code class="language-storybook">use schema::enums::{PlaceType, Accessibility};
|
||
|
||
location BakersBakery {
|
||
type: shop
|
||
accessibility: public
|
||
owner: Martha
|
||
address: "14 Main Street"
|
||
capacity: 30
|
||
employees: 4
|
||
established: "2011"
|
||
specialty: "artisan sourdough"
|
||
daily_output_loaves: 80..120
|
||
|
||
---description
|
||
Martha Baker's artisan bakery, known throughout town for its
|
||
sourdough and pastries. The shop opens at 6 AM sharp, and by
|
||
mid-morning there's usually a line out the door.
|
||
---
|
||
|
||
---history
|
||
Originally a hardware store, Martha converted the space after
|
||
winning a local baking competition. The stone oven was imported
|
||
from France and is the heart of the operation.
|
||
---
|
||
}
|
||
|
||
location BakerHome {
|
||
type: residence
|
||
accessibility: private
|
||
address: "22 Elm Lane"
|
||
bedrooms: 4
|
||
has_garden: true
|
||
garden_size_sqft: 600
|
||
residents: ["Martha", "David", "Jane", "Tom"]
|
||
comfort_level: 0.9
|
||
|
||
---description
|
||
A comfortable family home on a quiet street. The kitchen is
|
||
oversized (Martha insisted) and there's always something
|
||
baking, even at home.
|
||
---
|
||
}
|
||
|
||
location BakersGuildHall {
|
||
type: office
|
||
accessibility: members_only
|
||
address: "7 Guild Row"
|
||
capacity: 100
|
||
meeting_room_capacity: 40
|
||
established: "1892"
|
||
|
||
---description
|
||
The historic headquarters of the Bakers Guild, where trade
|
||
matters are discussed and apprenticeships are arranged.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="validation-rules-6"><a class="header" href="#validation-rules-6">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>Unique names</strong>: Location names must be unique within their scope</li>
|
||
<li><strong>Valid field values</strong>: All fields must have values that conform to <a href="reference/./18-value-types.html">value types</a></li>
|
||
<li><strong>Unique field names</strong>: No duplicate field names within a location</li>
|
||
<li><strong>Unique prose tags</strong>: No duplicate prose block tags within a location</li>
|
||
<li><strong>Valid identifiers</strong>: Location names must follow identifier rules (<code>[a-zA-Z_][a-zA-Z0-9_]*</code>)</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="locations-vs-other-declarations"><a class="header" href="#locations-vs-other-declarations">Locations vs. Other Declarations</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Aspect</th><th>Locations</th><th>Institutions</th><th>Characters</th></tr></thead><tbody>
|
||
<tr><td>Purpose</td><td>Physical/abstract places</td><td>Organizations/groups</td><td>Individuals</td></tr>
|
||
<tr><td>Resource linking</td><td>No</td><td>Yes</td><td>Yes</td></tr>
|
||
<tr><td>Prose blocks</td><td>Yes</td><td>Yes</td><td>Yes</td></tr>
|
||
<tr><td>Species</td><td>No</td><td>No</td><td>Yes</td></tr>
|
||
<tr><td>Templates</td><td>No</td><td>No</td><td>Yes</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>Locations are intentionally simple. They define <em>where</em> things happen. For <em>who</em> does things, use <a href="reference/./10-characters.html">characters</a>. For <em>organizational structures</em>, use <a href="reference/./16b-institutions.html">institutions</a>.</p>
|
||
<hr />
|
||
<h2 id="cross-references-6"><a class="header" href="#cross-references-6">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> – Characters can reference locations via fields</li>
|
||
<li><a href="reference/./16b-institutions.html">Institutions</a> – Institutions can be associated with locations</li>
|
||
<li><a href="reference/./14-schedules.html">Schedules</a> – Schedule activities can reference locations</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> – All field value types</li>
|
||
<li><a href="reference/./16-other-declarations.html">Other Declarations</a> – Overview of all utility declarations</li>
|
||
<li><a href="reference/./19-validation.html">Validation Rules</a> – Complete validation reference</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="institutions"><a class="header" href="#institutions">Institutions</a></h1>
|
||
<p>Institutions define organizations, groups, and systems – entities that function like characters but represent collectives rather than individuals. Unlike locations (which model <em>where</em>), institutions model <em>what structures and organizations</em> operate in your world.</p>
|
||
<hr />
|
||
<h2 id="syntax-14"><a class="header" href="#syntax-14">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><institution-decl> ::= "institution" <identifier> "{" <institution-body> "}"
|
||
|
||
<institution-body> ::= (<field> | <uses-behaviors> | <uses-schedule> | <prose-block>)*
|
||
|
||
<uses-behaviors> ::= "uses" "behaviors" ":" "[" <behavior-link> ("," <behavior-link>)* "]"
|
||
|
||
<behavior-link> ::= "{" "tree" ":" <path> ","?
|
||
("when" ":" <expression> ","?)?
|
||
("priority" ":" <priority-level> ","?)? "}"
|
||
|
||
<priority-level> ::= "low" | "normal" | "high" | "critical"
|
||
|
||
<uses-schedule> ::= "uses" "schedule" ":" <identifier>
|
||
| "uses" "schedules" ":" "[" <identifier> ("," <identifier>)* "]"
|
||
</code></pre>
|
||
<p>An institution declaration consists of:</p>
|
||
<ul>
|
||
<li>The <code>institution</code> keyword</li>
|
||
<li>A unique name (identifier)</li>
|
||
<li>A body block containing fields, resource links, and optional prose blocks</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="basic-institution"><a class="header" href="#basic-institution">Basic Institution</a></h2>
|
||
<p>The simplest institution has a name and descriptive fields:</p>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
members: 50
|
||
founded: "1892"
|
||
reputation: 0.85
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="fields-2"><a class="header" href="#fields-2">Fields</a></h2>
|
||
<p>Institution fields describe properties of the organization:</p>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
members: 50
|
||
founded: "1892"
|
||
reputation: 0.85
|
||
dues_annual: 120
|
||
meeting_frequency: "monthly"
|
||
accepts_apprentices: true
|
||
max_apprentices: 10
|
||
location: BakersGuildHall
|
||
}
|
||
</code></pre>
|
||
<h3 id="common-field-patterns-1"><a class="header" href="#common-field-patterns-1">Common Field Patterns</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody>
|
||
<tr><td><code>type</code></td><td>enum/identifier</td><td>Category of organization</td></tr>
|
||
<tr><td><code>members</code></td><td>integer</td><td>Membership count</td></tr>
|
||
<tr><td><code>founded</code></td><td>string</td><td>Establishment date</td></tr>
|
||
<tr><td><code>reputation</code></td><td>float</td><td>Public standing (0.0-1.0)</td></tr>
|
||
<tr><td><code>location</code></td><td>identifier</td><td>Where the institution operates</td></tr>
|
||
<tr><td><code>leader</code></td><td>identifier</td><td>Who runs the institution</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>These are conventions – you define whatever fields make sense for your world.</p>
|
||
<hr />
|
||
<h2 id="prose-blocks-5"><a class="header" href="#prose-blocks-5">Prose Blocks</a></h2>
|
||
<p>Institutions support prose blocks for rich narrative documentation:</p>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
members: 50
|
||
|
||
---description
|
||
The Bakers Guild has been the backbone of the town's bread trade
|
||
since 1892. Members share recipes, arrange apprenticeships, and
|
||
collectively negotiate flour prices with suppliers.
|
||
---
|
||
|
||
---charter
|
||
Article I: All members shall maintain the highest standards of
|
||
baking quality. Article II: Apprentices must complete a three-year
|
||
training program. Article III: Monthly meetings are mandatory.
|
||
---
|
||
|
||
---traditions
|
||
Every autumn, the Guild hosts the Great Bake-Off, a competition
|
||
open to all members. The winner earns the title of Master Baker
|
||
for the following year.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<p><strong>Prose block rules:</strong></p>
|
||
<ul>
|
||
<li>Start with <code>---tag_name</code> on its own line</li>
|
||
<li>Content is free-form text (Markdown supported)</li>
|
||
<li>End with <code>---</code> on its own line</li>
|
||
<li>Multiple prose blocks per institution are allowed</li>
|
||
<li>Each tag must be unique within the institution</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="resource-linking-behaviors"><a class="header" href="#resource-linking-behaviors">Resource Linking: Behaviors</a></h2>
|
||
<p>Institutions can link to <a href="reference/./11-behavior-trees.html">behavior trees</a> using the <code>uses behaviors</code> clause. This defines what actions the institution takes as a collective entity.</p>
|
||
<h3 id="simple-behavior-linking"><a class="header" href="#simple-behavior-linking">Simple Behavior Linking</a></h3>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
members: 50
|
||
|
||
uses behaviors: [
|
||
{ tree: ManageApprentices },
|
||
{ tree: NegotiateSuppliers },
|
||
{ tree: HostEvents }
|
||
]
|
||
}
|
||
</code></pre>
|
||
<p>Each behavior link is an object with a <code>tree</code> field referencing a behavior tree by name or path.</p>
|
||
<h3 id="behavior-links-with-priority"><a class="header" href="#behavior-links-with-priority">Behavior Links with Priority</a></h3>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
|
||
uses behaviors: [
|
||
{ tree: ManageApprentices, priority: normal },
|
||
{ tree: NegotiateSuppliers, priority: high },
|
||
{ tree: HostEvents, priority: low }
|
||
]
|
||
}
|
||
</code></pre>
|
||
<p>Priority levels: <code>low</code>, <code>normal</code> (default), <code>high</code>, <code>critical</code>.</p>
|
||
<h3 id="conditional-behavior-links"><a class="header" href="#conditional-behavior-links">Conditional Behavior Links</a></h3>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
|
||
uses behaviors: [
|
||
{ tree: ManageApprentices },
|
||
{ tree: NegotiateSuppliers, priority: high },
|
||
{ tree: EmergencyMeeting, when: reputation < 0.3, priority: critical }
|
||
]
|
||
}
|
||
</code></pre>
|
||
<p>The <code>when</code> clause takes an <a href="reference/./17-expressions.html">expression</a> that determines when the behavior activates.</p>
|
||
<h3 id="behavior-link-fields"><a class="header" href="#behavior-link-fields">Behavior Link Fields</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Field</th><th>Required</th><th>Type</th><th>Description</th></tr></thead><tbody>
|
||
<tr><td><code>tree</code></td><td>Yes</td><td>path</td><td>Reference to a behavior tree</td></tr>
|
||
<tr><td><code>priority</code></td><td>No</td><td>priority level</td><td>Execution priority (default: <code>normal</code>)</td></tr>
|
||
<tr><td><code>when</code></td><td>No</td><td>expression</td><td>Activation condition</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<hr />
|
||
<h2 id="resource-linking-schedules"><a class="header" href="#resource-linking-schedules">Resource Linking: Schedules</a></h2>
|
||
<p>Institutions can link to <a href="reference/./14-schedules.html">schedules</a> using the <code>uses schedule</code> or <code>uses schedules</code> clause.</p>
|
||
<h3 id="single-schedule"><a class="header" href="#single-schedule">Single Schedule</a></h3>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
uses schedule: GuildOperatingHours
|
||
}
|
||
</code></pre>
|
||
<h3 id="multiple-schedules"><a class="header" href="#multiple-schedules">Multiple Schedules</a></h3>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
uses schedules: [WeekdaySchedule, WeekendSchedule, HolidaySchedule]
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="complete-syntax-example"><a class="header" href="#complete-syntax-example">Complete Syntax Example</a></h2>
|
||
<p>An institution using all available features:</p>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
members: 50
|
||
founded: "1892"
|
||
reputation: 0.85
|
||
dues_annual: 120
|
||
location: BakersGuildHall
|
||
leader: Martha
|
||
accepts_apprentices: true
|
||
|
||
uses behaviors: [
|
||
{ tree: ManageApprentices, priority: normal },
|
||
{ tree: NegotiateSuppliers, priority: high },
|
||
{ tree: HostAnnualBakeOff, when: month is october, priority: high },
|
||
{ tree: EmergencyMeeting, when: reputation < 0.3, priority: critical }
|
||
]
|
||
|
||
uses schedule: GuildOperatingHours
|
||
|
||
---description
|
||
The Bakers Guild has been the backbone of the town's bread trade
|
||
since 1892. Martha Baker currently serves as Guild Master.
|
||
---
|
||
|
||
---membership_rules
|
||
New members must be nominated by an existing member and pass
|
||
a baking trial. Apprentices are accepted from age 16.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="membership-modeling"><a class="header" href="#membership-modeling">Membership Modeling</a></h2>
|
||
<p>Institutions themselves don’t have a built-in membership list. Instead, model membership through character fields or relationships:</p>
|
||
<h3 id="via-character-fields"><a class="header" href="#via-character-fields">Via Character Fields</a></h3>
|
||
<pre><code class="language-storybook">character Martha {
|
||
age: 45
|
||
guild_member: true
|
||
guild_role: guild_master
|
||
guild: BakersGuild
|
||
}
|
||
|
||
character Jane {
|
||
age: 19
|
||
guild_member: true
|
||
guild_role: apprentice
|
||
guild: BakersGuild
|
||
}
|
||
</code></pre>
|
||
<h3 id="via-relationships"><a class="header" href="#via-relationships">Via Relationships</a></h3>
|
||
<pre><code class="language-storybook">relationship GuildMembership {
|
||
Martha as guild_master { }
|
||
BakersGuild as organization { }
|
||
bond: 0.95
|
||
years_active: 20
|
||
}
|
||
|
||
relationship Apprenticeship {
|
||
Jane as apprentice { }
|
||
BakersGuild as guild { }
|
||
Martha as mentor { }
|
||
years_completed: 1
|
||
years_remaining: 2
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="institutions-vs-characters"><a class="header" href="#institutions-vs-characters">Institutions vs. Characters</a></h2>
|
||
<p>Both institutions and characters can use behaviors and schedules, but they serve different purposes:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Aspect</th><th>Institutions</th><th>Characters</th></tr></thead><tbody>
|
||
<tr><td>Represents</td><td>Organizations, collectives</td><td>Individuals</td></tr>
|
||
<tr><td>Species</td><td>No</td><td>Yes (optional)</td></tr>
|
||
<tr><td>Templates</td><td>No</td><td>Yes (optional)</td></tr>
|
||
<tr><td><code>uses behaviors</code></td><td>Yes</td><td>Yes</td></tr>
|
||
<tr><td><code>uses schedule</code></td><td>Yes</td><td>Yes</td></tr>
|
||
<tr><td>Prose blocks</td><td>Yes</td><td>Yes</td></tr>
|
||
<tr><td>Participation in relationships</td><td>Yes (via name)</td><td>Yes (via name)</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>Use institutions when you need an entity that acts collectively: a guild votes, a government issues decrees, a school enrolls students.</p>
|
||
<hr />
|
||
<h2 id="use-cases-2"><a class="header" href="#use-cases-2">Use Cases</a></h2>
|
||
<ul>
|
||
<li><strong>Organizations</strong>: Guilds, companies, governments, religions</li>
|
||
<li><strong>Social structures</strong>: Families, tribes, castes, classes</li>
|
||
<li><strong>Systems</strong>: Economic systems, magical systems, legal frameworks</li>
|
||
<li><strong>Abstract entities</strong>: Dream logic, fate, cultural norms</li>
|
||
<li><strong>Collectives</strong>: Military units, sports teams, musical ensembles</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="complete-example-baker-family-world"><a class="header" href="#complete-example-baker-family-world">Complete Example: Baker Family World</a></h2>
|
||
<pre><code class="language-storybook">use schema::enums::{GuildRole, InstitutionType};
|
||
|
||
institution BakersGuild {
|
||
type: trade_guild
|
||
members: 50
|
||
founded: "1892"
|
||
reputation: 0.85
|
||
location: BakersGuildHall
|
||
leader: Martha
|
||
annual_dues: 120
|
||
accepts_apprentices: true
|
||
max_apprentices_per_mentor: 2
|
||
|
||
uses behaviors: [
|
||
{ tree: ManageApprentices, priority: normal },
|
||
{ tree: NegotiateSuppliers, priority: high },
|
||
{ tree: HostAnnualBakeOff, when: month is october, priority: high }
|
||
]
|
||
|
||
uses schedule: GuildOperatingHours
|
||
|
||
---description
|
||
The Bakers Guild has served the town since 1892, ensuring quality
|
||
standards and fair pricing across all bakeries. Monthly meetings
|
||
are held at the Guild Hall, and the annual Bake-Off is the
|
||
highlight of the autumn calendar.
|
||
---
|
||
}
|
||
|
||
institution TownCouncil {
|
||
type: government
|
||
members: 12
|
||
meets: "first Monday of each month"
|
||
location: TownHall
|
||
jurisdiction: "town limits"
|
||
|
||
uses behaviors: [
|
||
{ tree: ReviewPetitions },
|
||
{ tree: AllocateBudget, priority: high }
|
||
]
|
||
|
||
---description
|
||
The elected body governing the town. Martha Baker has been
|
||
lobbying them for years about flour import tariffs.
|
||
---
|
||
}
|
||
|
||
institution BakersBakeryBusiness {
|
||
type: business
|
||
owner: Martha
|
||
employees: 4
|
||
location: BakersBakery
|
||
annual_revenue: 180000
|
||
established: "2011"
|
||
|
||
uses behaviors: [
|
||
{ tree: DailyBakingOps, priority: high },
|
||
{ tree: InventoryManagement },
|
||
{ tree: CustomerService }
|
||
]
|
||
|
||
uses schedule: BakeryOperatingHours
|
||
|
||
---description
|
||
The business entity behind Baker's Bakery. Martha runs the
|
||
operation with help from her daughter Jane (apprentice baker),
|
||
two full-time bakers, and a part-time cashier.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="validation-rules-7"><a class="header" href="#validation-rules-7">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>Unique names</strong>: Institution names must be unique within their scope</li>
|
||
<li><strong>Valid field values</strong>: All fields must have values conforming to <a href="reference/./18-value-types.html">value types</a></li>
|
||
<li><strong>Unique field names</strong>: No duplicate field names within an institution</li>
|
||
<li><strong>Unique prose tags</strong>: No duplicate prose block tags within an institution</li>
|
||
<li><strong>Valid behavior links</strong>: Each behavior link must have a <code>tree</code> field</li>
|
||
<li><strong>Valid priority levels</strong>: Priority must be <code>low</code>, <code>normal</code>, <code>high</code>, or <code>critical</code></li>
|
||
<li><strong>Valid schedule references</strong>: Schedule names in <code>uses schedule</code>/<code>uses schedules</code> must be valid identifiers</li>
|
||
<li><strong>Valid identifiers</strong>: Institution names must follow identifier rules (<code>[a-zA-Z_][a-zA-Z0-9_]*</code>)</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="cross-references-7"><a class="header" href="#cross-references-7">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> – Characters can be members of institutions</li>
|
||
<li><a href="reference/./16a-locations.html">Locations</a> – Institutions can be associated with locations</li>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> – Institutions can use behaviors</li>
|
||
<li><a href="reference/./14-schedules.html">Schedules</a> – Institutions can use schedules</li>
|
||
<li><a href="reference/./15-relationships.html">Relationships</a> – Model membership via relationships</li>
|
||
<li><a href="reference/./17-expressions.html">Expressions</a> – Conditional behavior activation</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> – All field value types</li>
|
||
<li><a href="reference/./16-other-declarations.html">Other Declarations</a> – Overview of all utility declarations</li>
|
||
<li><a href="reference/./19-validation.html">Validation Rules</a> – Complete validation reference</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="other-declarations"><a class="header" href="#other-declarations">Other Declarations</a></h1>
|
||
<p>This chapter covers six utility declaration types that complete the Storybook language: Templates, Institutions, Locations, Species, Enums, and Use statements. These declarations enable code reuse, organizational modeling, world-building, type safety, and modular file organization.</p>
|
||
<hr />
|
||
<h2 id="templates"><a class="header" href="#templates">Templates</a></h2>
|
||
<p>Templates are reusable field sets that characters inherit using the <code>from</code> keyword. Unlike <a href="reference/./10-characters.html#species-optional">species</a> (which define what an entity <em>is</em>), templates define what an entity <em>has</em>—capabilities, traits, and characteristics.</p>
|
||
<h3 id="syntax-15"><a class="header" href="#syntax-15">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><template-decl> ::= "template" <identifier> <strict-clause>? <resource-links>? <includes-clause>? <body>
|
||
|
||
<strict-clause> ::= "strict"
|
||
|
||
<resource-links> ::= "uses" "behaviors" ":" <behavior-list>
|
||
| "uses" "schedule" ":" <schedule-ref>
|
||
| "uses" "schedules" ":" <schedule-list>
|
||
|
||
<includes-clause> ::= "include" <identifier> ("," <identifier>)*
|
||
|
||
<body> ::= "{" <field>* "}"
|
||
|
||
<field> ::= <identifier> ":" <value>
|
||
</code></pre>
|
||
<h3 id="basic-template"><a class="header" href="#basic-template">Basic Template</a></h3>
|
||
<pre><code class="language-storybook">template Warrior {
|
||
strength: 10..20
|
||
dexterity: 8..15
|
||
weapon_proficiency: 0.7..1.0
|
||
}
|
||
</code></pre>
|
||
<p>Characters inheriting this template get these fields with values selected from the specified ranges.</p>
|
||
<h3 id="template-includes"><a class="header" href="#template-includes">Template Includes</a></h3>
|
||
<p>Templates can include other templates to compose functionality:</p>
|
||
<pre><code class="language-storybook">template SkilledWorker {
|
||
skill_level: SkillLevel
|
||
confidence: Confidence
|
||
years_experience: 0..50
|
||
can_work_independently: false
|
||
}
|
||
|
||
template Baker {
|
||
include SkilledWorker
|
||
|
||
specialty: Specialty
|
||
recipes_mastered: 0..200
|
||
sourdough_starter_health: 0.0..1.0
|
||
}
|
||
</code></pre>
|
||
<p><strong>Semantics:</strong></p>
|
||
<ul>
|
||
<li><code>include SkilledWorker</code> brings in all fields from that template</li>
|
||
<li>Fields are merged left-to-right (later overrides earlier)</li>
|
||
<li>Transitive includes supported</li>
|
||
<li>Multiple includes allowed: <code>include A, B, C</code></li>
|
||
</ul>
|
||
<h3 id="range-values"><a class="header" href="#range-values">Range Values</a></h3>
|
||
<p>Templates can specify ranges for procedural variation:</p>
|
||
<pre><code class="language-storybook">template Villager {
|
||
age: 18..65
|
||
wealth: 10..100
|
||
height: 150.0..190.0
|
||
disposition: 0.0..1.0
|
||
}
|
||
</code></pre>
|
||
<p><strong>Range syntax:</strong></p>
|
||
<ul>
|
||
<li>Integer ranges: <code>min..max</code> (inclusive)</li>
|
||
<li>Float ranges: <code>min..max</code> (inclusive)</li>
|
||
<li>Both bounds must be same type</li>
|
||
<li>min ≤ max required</li>
|
||
</ul>
|
||
<p>When a character uses this template, the runtime selects specific values within each range.</p>
|
||
<h3 id="strict-mode"><a class="header" href="#strict-mode">Strict Mode</a></h3>
|
||
<p>Strict templates enforce that characters using them can only have fields defined in the template:</p>
|
||
<pre><code class="language-storybook">template RecipeCard strict {
|
||
include SkilledWorker
|
||
|
||
recipe_name: string
|
||
difficulty: Difficulty
|
||
prep_time_minutes: 10..180
|
||
requires_starter: false
|
||
}
|
||
</code></pre>
|
||
<p><strong>Strict semantics:</strong></p>
|
||
<ul>
|
||
<li>Characters using strict templates cannot add extra fields</li>
|
||
<li>All template fields must be present in the character</li>
|
||
<li>Enables type safety for well-defined schemas</li>
|
||
<li>Use for controlled domains (game cards, rigid categories)</li>
|
||
</ul>
|
||
<p><strong>Non-strict (default):</strong></p>
|
||
<pre><code class="language-storybook">template Flexible {
|
||
base_stat: 10
|
||
}
|
||
|
||
character Custom from Flexible {
|
||
base_stat: 15 // Override
|
||
extra_field: 42 // Allowed in non-strict
|
||
}
|
||
</code></pre>
|
||
<p><strong>Strict:</strong></p>
|
||
<pre><code class="language-storybook">template Rigid strict {
|
||
required_stat: 10
|
||
}
|
||
|
||
character Constrained from Rigid {
|
||
required_stat: 15 // OK: Override
|
||
extra_field: 42 // ERROR: Not allowed in strict template
|
||
}
|
||
</code></pre>
|
||
<h3 id="resource-linking-in-templates"><a class="header" href="#resource-linking-in-templates">Resource Linking in Templates</a></h3>
|
||
<p>Templates can link to behaviors and schedules (v0.2.0+):</p>
|
||
<pre><code class="language-storybook">template BakeryStaffMember
|
||
uses behaviors: DailyBakingRoutine, CustomerService
|
||
uses schedule: BakerySchedule
|
||
{
|
||
include SkilledWorker
|
||
|
||
on_shift: true
|
||
orders_completed: 0..1000
|
||
current_station: 0..4
|
||
}
|
||
</code></pre>
|
||
<p>Characters inheriting this template automatically get the linked behaviors and schedule.</p>
|
||
<h3 id="templates-vs-species"><a class="header" href="#templates-vs-species">Templates vs. Species</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Aspect</th><th>Templates (<code>from</code>)</th><th>Species (<code>:</code>)</th></tr></thead><tbody>
|
||
<tr><td>Semantics</td><td>What entity <em>has</em></td><td>What entity <em>is</em></td></tr>
|
||
<tr><td>Cardinality</td><td>Multiple inheritance</td><td>Single inheritance</td></tr>
|
||
<tr><td>Ranges</td><td>Allowed</td><td>Not allowed</td></tr>
|
||
<tr><td>Strict mode</td><td>Supported</td><td>Not supported</td></tr>
|
||
<tr><td>Use case</td><td>Compositional traits</td><td>Ontological identity</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Example combining both:</strong></p>
|
||
<pre><code class="language-storybook">species Dragon {
|
||
lifespan: 1000
|
||
can_fly: true
|
||
}
|
||
|
||
template Hoarder {
|
||
treasure_value: 0..1000000
|
||
greed_level: 0.5..1.0
|
||
}
|
||
|
||
character Smaug: Dragon from Hoarder {
|
||
age: 850
|
||
greed_level: 0.95
|
||
}
|
||
</code></pre>
|
||
<h3 id="validation-rules-8"><a class="header" href="#validation-rules-8">Validation Rules</a></h3>
|
||
<ol>
|
||
<li><strong>Includes exist</strong>: All included templates must be defined</li>
|
||
<li><strong>No circular includes</strong>: Cannot form cycles</li>
|
||
<li><strong>Range validity</strong>: min ≤ max for all ranges</li>
|
||
<li><strong>Strict enforcement</strong>: Strict templates reject extra fields in characters</li>
|
||
<li><strong>Resource links valid</strong>: Behavior/schedule references must resolve</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="institutions-1"><a class="header" href="#institutions-1">Institutions</a></h2>
|
||
<p>Institutions define organizations, groups, and systems—entities that function like characters but represent collectives rather than individuals.</p>
|
||
<h3 id="syntax-16"><a class="header" href="#syntax-16">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><institution-decl> ::= "institution" <identifier> <resource-links>? <body>
|
||
|
||
<resource-links> ::= "uses" "behaviors" ":" <behavior-list>
|
||
| "uses" "schedule" ":" <schedule-ref>
|
||
| "uses" "schedules" ":" <schedule-list>
|
||
|
||
<body> ::= "{" <field>* <prose-block>* "}"
|
||
</code></pre>
|
||
<h3 id="basic-institution-1"><a class="header" href="#basic-institution-1">Basic Institution</a></h3>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: trade_guild
|
||
members: 50
|
||
founded: "1450-03-15"
|
||
reputation: 0.85
|
||
}
|
||
</code></pre>
|
||
<h3 id="institutions-with-fields"><a class="header" href="#institutions-with-fields">Institutions with Fields</a></h3>
|
||
<pre><code class="language-storybook">institution BakersGuild {
|
||
type: professional_guild
|
||
government_style: elected_board
|
||
hierarchy: flat
|
||
standards_enforcement: true
|
||
|
||
// Leadership
|
||
board_chair: Martha
|
||
vice_chair: Henri
|
||
board_temperament: collegial
|
||
|
||
// Membership
|
||
master_bakers: 12
|
||
journeymen: 25
|
||
apprentices: 8
|
||
honorary_members: 3
|
||
|
||
// Standards
|
||
certification_process: "practical exam and peer review"
|
||
quality_standard: "excellence"
|
||
annual_competition: true
|
||
scholarships_offered: 2
|
||
|
||
---description
|
||
The local bakers' guild that sets quality standards, organizes
|
||
competitions, and mentors apprentices. Martha has been a board
|
||
member for three years.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="resource-linking"><a class="header" href="#resource-linking">Resource Linking</a></h3>
|
||
<p>Institutions can use behaviors and schedules:</p>
|
||
<pre><code class="language-storybook">institution MarthasBakery
|
||
uses behaviors: DailyBakingRoutine, CustomerService
|
||
uses schedule: BakerySchedule
|
||
{
|
||
type: small_business
|
||
purpose: bread_and_pastry_production
|
||
family_owned: true
|
||
established: 2018
|
||
|
||
permanent_staff: 4
|
||
seasonal_helpers: 0..3
|
||
|
||
---description
|
||
A beloved neighborhood bakery run by Martha and Jane,
|
||
known for its sourdough bread and artisan pastries.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="prose-blocks-6"><a class="header" href="#prose-blocks-6">Prose Blocks</a></h3>
|
||
<p>Institutions support rich narrative documentation:</p>
|
||
<pre><code class="language-storybook">institution TownCulinaryScene {
|
||
type: cultural_ecosystem
|
||
governs: "local food culture"
|
||
|
||
// Characteristics
|
||
farm_to_table: true
|
||
artisan_focus: strong
|
||
seasonal_menus: true
|
||
community_events: monthly
|
||
|
||
---description
|
||
The overarching culinary culture of the town -- a network
|
||
of bakeries, farms, and food artisans that sustain each other.
|
||
---
|
||
|
||
---philosophy
|
||
The town's food scene operates on relationships: farmers
|
||
supply bakers, bakers feed families, families support farms.
|
||
Quality and trust are the currency of this ecosystem.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-cases-3"><a class="header" href="#use-cases-3">Use Cases</a></h3>
|
||
<ul>
|
||
<li><strong>Organizations</strong>: Guilds, companies, governments</li>
|
||
<li><strong>Systems</strong>: Magical systems, physical laws, economies</li>
|
||
<li><strong>Social structures</strong>: Families, tribes, castes</li>
|
||
<li><strong>Abstract entities</strong>: Dream logic, fate, chaos</li>
|
||
</ul>
|
||
<h3 id="validation-rules-9"><a class="header" href="#validation-rules-9">Validation Rules</a></h3>
|
||
<ol>
|
||
<li><strong>Unique names</strong>: Institution names must be unique</li>
|
||
<li><strong>Resource links valid</strong>: Behaviors/schedules must exist</li>
|
||
<li><strong>Field types</strong>: All fields must have valid values</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="locations-1"><a class="header" href="#locations-1">Locations</a></h2>
|
||
<p>Locations define places in your world—rooms, buildings, cities, landscapes, or abstract spaces.</p>
|
||
<h3 id="syntax-17"><a class="header" href="#syntax-17">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><location-decl> ::= "location" <identifier> <body>
|
||
|
||
<body> ::= "{" <field>* <prose-block>* "}"
|
||
</code></pre>
|
||
<h3 id="basic-location-1"><a class="header" href="#basic-location-1">Basic Location</a></h3>
|
||
<pre><code class="language-storybook">location MarthasBakery {
|
||
square_feet: 1200
|
||
type: commercial
|
||
established: "2018"
|
||
has_storefront: true
|
||
|
||
---description
|
||
A warm, inviting bakery on Main Street. The aroma of fresh
|
||
bread wafts out the door every morning. Exposed brick walls,
|
||
a glass display case, and a view into the open kitchen.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="location-with-structure"><a class="header" href="#location-with-structure">Location with Structure</a></h3>
|
||
<pre><code class="language-storybook">location BakeryKitchen {
|
||
type: "commercial kitchen"
|
||
ovens: 3
|
||
prep_stations: 4
|
||
walk_in_cooler: true
|
||
|
||
// Equipment
|
||
has_proofing_cabinet: true
|
||
mixer_capacity_kg: 20
|
||
starter_shelf: true
|
||
|
||
// Storage
|
||
flour_bins: 6
|
||
ingredient_shelves: 12
|
||
cold_storage: true
|
||
|
||
---description
|
||
The heart of the bakery. Three professional ovens line the
|
||
back wall. The sourdough starter sits on a shelf near the
|
||
warmest oven, bubbling contentedly in its ceramic crock.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="geographic-hierarchy"><a class="header" href="#geographic-hierarchy">Geographic Hierarchy</a></h3>
|
||
<p>Locations can reference other locations:</p>
|
||
<pre><code class="language-storybook">location MainStreet {
|
||
type: commercial_district
|
||
shops: 15
|
||
foot_traffic: high
|
||
}
|
||
|
||
location BakeryStorefront {
|
||
part_of: MainStreet
|
||
seating_capacity: 12
|
||
display_case: true
|
||
|
||
---description
|
||
The customer-facing area with a glass display case full
|
||
of fresh bread, pastries, and seasonal specials.
|
||
---
|
||
}
|
||
|
||
location FarmersMarket {
|
||
part_of: MainStreet
|
||
operates_on: saturday
|
||
stalls: 30
|
||
|
||
---description
|
||
The weekly Saturday market where Martha sells bread directly
|
||
to the community. Her stall is always the first to sell out.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="location-with-state"><a class="header" href="#location-with-state">Location with State</a></h3>
|
||
<pre><code class="language-storybook">location BakeryWarehouse {
|
||
temperature: controlled
|
||
square_feet: 400
|
||
humidity_controlled: true
|
||
shelving_units: 8
|
||
|
||
// Storage state
|
||
flour_stock_kg: 200
|
||
yeast_supply_days: 14
|
||
packaging_materials: true
|
||
|
||
---description
|
||
The storage area behind the kitchen. Climate-controlled to
|
||
keep flour dry and ingredients fresh. Martha takes inventory
|
||
every Monday morning.
|
||
---
|
||
|
||
---logistics
|
||
Deliveries arrive Tuesday and Friday mornings. The walk-in
|
||
cooler holds butter, eggs, and cream. Dry goods are rotated
|
||
on a first-in-first-out basis.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-cases-4"><a class="header" href="#use-cases-4">Use Cases</a></h3>
|
||
<ul>
|
||
<li><strong>Physical places</strong>: Cities, buildings, rooms</li>
|
||
<li><strong>Geographic features</strong>: Mountains, rivers, forests</li>
|
||
<li><strong>Abstract spaces</strong>: Dream realms, pocket dimensions</li>
|
||
<li><strong>Game boards</strong>: Arenas, dungeons, maps</li>
|
||
</ul>
|
||
<h3 id="validation-rules-10"><a class="header" href="#validation-rules-10">Validation Rules</a></h3>
|
||
<ol>
|
||
<li><strong>Unique names</strong>: Location names must be unique</li>
|
||
<li><strong>Field types</strong>: All fields must have valid values</li>
|
||
<li><strong>References valid</strong>: Location references (like <code>part_of</code>) should resolve</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="species"><a class="header" href="#species">Species</a></h2>
|
||
<p>Species define the fundamental ontological categories of beings. A species represents what an entity <em>is</em> at its core—human, dragon, sentient tree, animated playing card.</p>
|
||
<h3 id="syntax-18"><a class="header" href="#syntax-18">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><species-decl> ::= "species" <identifier> <includes-clause>? <body>
|
||
|
||
<includes-clause> ::= "includes" <identifier> ("," <identifier>)*
|
||
|
||
<body> ::= "{" <field>* <prose-block>* "}"
|
||
</code></pre>
|
||
<h3 id="basic-species"><a class="header" href="#basic-species">Basic Species</a></h3>
|
||
<pre><code class="language-storybook">species Human {
|
||
lifespan: 70
|
||
|
||
---description
|
||
Bipedal mammals with complex language and tool use.
|
||
Highly variable in cultures and capabilities.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="species-with-includes"><a class="header" href="#species-with-includes">Species with Includes</a></h3>
|
||
<p>Species can include other species for composition:</p>
|
||
<pre><code class="language-storybook">species Mammal {
|
||
warm_blooded: true
|
||
has_fur: true
|
||
}
|
||
|
||
species Primate includes Mammal {
|
||
opposable_thumbs: true
|
||
sapience_potential: 0.5..1.0
|
||
}
|
||
|
||
species Human includes Primate {
|
||
sapience_potential: 1.0
|
||
language_complexity: "high"
|
||
|
||
---description
|
||
Highly intelligent primates with advanced tool use.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="field-resolution-with-includes"><a class="header" href="#field-resolution-with-includes">Field Resolution with Includes</a></h3>
|
||
<p>When a species includes others, fields are merged in declaration order:</p>
|
||
<ol>
|
||
<li><strong>Base species</strong> (leftmost in <code>includes</code>)</li>
|
||
<li><strong>Middle species</strong></li>
|
||
<li><strong>Rightmost species</strong></li>
|
||
<li><strong>Current species</strong> (highest priority)</li>
|
||
</ol>
|
||
<p>Example:</p>
|
||
<pre><code class="language-storybook">species Aquatic {
|
||
breathes_underwater: true
|
||
speed_in_water: 2.0
|
||
}
|
||
|
||
species Reptile {
|
||
cold_blooded: true
|
||
speed_in_water: 1.0
|
||
}
|
||
|
||
species SeaTurtle includes Aquatic, Reptile {
|
||
has_shell: true
|
||
speed_in_water: 1.5 // Overrides both
|
||
}
|
||
|
||
// Resolved:
|
||
// breathes_underwater: true (from Aquatic)
|
||
// cold_blooded: true (from Reptile)
|
||
// speed_in_water: 1.5 (SeaTurtle overrides Reptile overrides Aquatic)
|
||
// has_shell: true (from SeaTurtle)
|
||
</code></pre>
|
||
<h3 id="species-vs-templates-2"><a class="header" href="#species-vs-templates-2">Species vs. Templates</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Aspect</th><th>Species (<code>:</code>)</th><th>Templates (<code>from</code>)</th></tr></thead><tbody>
|
||
<tr><td>Question</td><td>“What <em>is</em> it?”</td><td>“What <em>traits</em> does it have?”</td></tr>
|
||
<tr><td>Cardinality</td><td>One per character</td><td>Zero or more</td></tr>
|
||
<tr><td>Inheritance</td><td><code>includes</code> (species → species)</td><td>Characters inherit from templates</td></tr>
|
||
<tr><td>Variation</td><td>Concrete defaults</td><td>Ranges allowed</td></tr>
|
||
<tr><td>Example</td><td><code>species Human</code></td><td><code>template Warrior</code></td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>When to use species:</strong></p>
|
||
<pre><code class="language-storybook">species Dragon {
|
||
lifespan: 1000
|
||
can_fly: true
|
||
breathes_fire: true
|
||
}
|
||
|
||
character Smaug: Dragon {
|
||
age: 850 // Smaug IS a Dragon
|
||
}
|
||
</code></pre>
|
||
<p><strong>When to use templates:</strong></p>
|
||
<pre><code class="language-storybook">template Hoarder {
|
||
treasure_value: 0..1000000
|
||
greed_level: 0.5..1.0
|
||
}
|
||
|
||
character Smaug: Dragon from Hoarder {
|
||
greed_level: 0.95 // Smaug HAS hoarder traits
|
||
}
|
||
</code></pre>
|
||
<h3 id="design-pattern-prefer-composition-over-deep-hierarchies"><a class="header" href="#design-pattern-prefer-composition-over-deep-hierarchies">Design Pattern: Prefer Composition Over Deep Hierarchies</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">species Being { ... }
|
||
species LivingBeing includes Being { ... }
|
||
species Animal includes LivingBeing { ... }
|
||
species Vertebrate includes Animal { ... }
|
||
species Mammal includes Vertebrate { ... }
|
||
species Primate includes Mammal { ... }
|
||
species Human includes Primate { ... } // Too deep!
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">species Mammal {
|
||
warm_blooded: true
|
||
live_birth: true
|
||
}
|
||
|
||
species Human includes Mammal {
|
||
sapient: true
|
||
}
|
||
|
||
// Use templates for capabilities
|
||
template Climber { ... }
|
||
template SocialCreature { ... }
|
||
|
||
character Jane: Human from Climber, SocialCreature { ... }
|
||
</code></pre>
|
||
<h3 id="validation-rules-11"><a class="header" href="#validation-rules-11">Validation Rules</a></h3>
|
||
<ol>
|
||
<li><strong>Unique names</strong>: Species names must be unique</li>
|
||
<li><strong>No circular includes</strong>: Cannot form cycles</li>
|
||
<li><strong>Includes exist</strong>: All included species must be defined</li>
|
||
<li><strong>Field types</strong>: All fields must have valid values</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="enums"><a class="header" href="#enums">Enums</a></h2>
|
||
<p>Enums define controlled vocabularies—fixed sets of named values. They enable type-safe categorization and validation.</p>
|
||
<h3 id="syntax-19"><a class="header" href="#syntax-19">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><enum-decl> ::= "enum" <identifier> "{" <variant>+ "}"
|
||
|
||
<variant> ::= <identifier> ","?
|
||
</code></pre>
|
||
<h3 id="basic-enum"><a class="header" href="#basic-enum">Basic Enum</a></h3>
|
||
<pre><code class="language-storybook">enum Size {
|
||
tiny,
|
||
small,
|
||
normal,
|
||
large,
|
||
huge
|
||
}
|
||
</code></pre>
|
||
<h3 id="using-enums"><a class="header" href="#using-enums">Using Enums</a></h3>
|
||
<p>Enums are used as field values throughout the system:</p>
|
||
<pre><code class="language-storybook">character Martha: Human {
|
||
skill_level: master // References SkillLevel enum
|
||
specialty: sourdough // References Specialty enum
|
||
}
|
||
</code></pre>
|
||
<h3 id="common-enum-patterns"><a class="header" href="#common-enum-patterns">Common Enum Patterns</a></h3>
|
||
<p><strong>Emotional States:</strong></p>
|
||
<pre><code class="language-storybook">enum EmotionalState {
|
||
curious,
|
||
frightened,
|
||
confused,
|
||
brave,
|
||
angry,
|
||
melancholy,
|
||
amused
|
||
}
|
||
</code></pre>
|
||
<p><strong>Card Suits:</strong></p>
|
||
<pre><code class="language-storybook">enum CardSuit {
|
||
hearts,
|
||
diamonds,
|
||
clubs,
|
||
spades
|
||
}
|
||
|
||
enum CardRank {
|
||
two, three, four, five, six, seven, eight, nine, ten,
|
||
knave, queen, king
|
||
}
|
||
</code></pre>
|
||
<p><strong>Time States:</strong></p>
|
||
<pre><code class="language-storybook">enum TimeState {
|
||
normal,
|
||
frozen,
|
||
reversed,
|
||
accelerated
|
||
}
|
||
</code></pre>
|
||
<p><strong>Government Types:</strong></p>
|
||
<pre><code class="language-storybook">enum GovernmentType {
|
||
monarchy,
|
||
democracy,
|
||
anarchy,
|
||
tyranny,
|
||
oligarchy
|
||
}
|
||
|
||
enum GovernmentStyle {
|
||
absolute_tyranny,
|
||
benevolent_dictatorship,
|
||
constitutional_monarchy,
|
||
direct_democracy,
|
||
representative_democracy
|
||
}
|
||
</code></pre>
|
||
<h3 id="calendar-enums-configurable"><a class="header" href="#calendar-enums-configurable">Calendar Enums (Configurable)</a></h3>
|
||
<p>Define custom calendars for your world:</p>
|
||
<pre><code class="language-storybook">enum Season {
|
||
spring,
|
||
summer,
|
||
fall,
|
||
winter
|
||
}
|
||
|
||
enum DayOfWeek {
|
||
monday,
|
||
tuesday,
|
||
wednesday,
|
||
thursday,
|
||
friday,
|
||
saturday,
|
||
sunday
|
||
}
|
||
|
||
enum Month {
|
||
january, february, march, april,
|
||
may, june, july, august,
|
||
september, october, november, december
|
||
}
|
||
</code></pre>
|
||
<p><strong>Custom calendars:</strong></p>
|
||
<pre><code class="language-storybook">enum EightSeasons {
|
||
deep_winter,
|
||
late_winter,
|
||
early_spring,
|
||
late_spring,
|
||
early_summer,
|
||
late_summer,
|
||
early_fall,
|
||
late_fall
|
||
}
|
||
</code></pre>
|
||
<h3 id="validation-integration"><a class="header" href="#validation-integration">Validation Integration</a></h3>
|
||
<p>Enums enable compile-time validation:</p>
|
||
<pre><code class="language-storybook">enum Size {
|
||
tiny, small, normal, large, huge
|
||
}
|
||
|
||
character Martha {
|
||
skill_level: medium // ERROR: 'medium' not in SkillLevel enum
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-cases-5"><a class="header" href="#use-cases-5">Use Cases</a></h3>
|
||
<ul>
|
||
<li><strong>Controlled vocabularies</strong>: Prevent typos and invalid values</li>
|
||
<li><strong>Schema constraints</strong>: Temporal systems, card decks, status types</li>
|
||
<li><strong>Type safety</strong>: Catch errors at compile time</li>
|
||
<li><strong>Documentation</strong>: Enumerate all valid options</li>
|
||
</ul>
|
||
<h3 id="validation-rules-12"><a class="header" href="#validation-rules-12">Validation Rules</a></h3>
|
||
<ol>
|
||
<li><strong>Unique enum names</strong>: Enum names must be unique</li>
|
||
<li><strong>Unique variants</strong>: Variant names must be unique within the enum</li>
|
||
<li><strong>Non-empty</strong>: Enums must have at least one variant</li>
|
||
<li><strong>Valid identifiers</strong>: Variants must follow identifier rules</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="use-statements"><a class="header" href="#use-statements">Use Statements</a></h2>
|
||
<p>Use statements import definitions from other files, enabling modular organization and code reuse.</p>
|
||
<h3 id="syntax-20"><a class="header" href="#syntax-20">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><use-decl> ::= "use" <use-path> <use-kind>
|
||
|
||
<use-path> ::= <identifier> ("::" <identifier>)*
|
||
|
||
<use-kind> ::= ";" // Single import
|
||
| "::{" <identifier> ("," <identifier>)* "}" ";" // Grouped import
|
||
| "::*" ";" // Wildcard import
|
||
</code></pre>
|
||
<h3 id="single-import"><a class="header" href="#single-import">Single Import</a></h3>
|
||
<pre><code class="language-storybook">use schema::beings::Human;
|
||
</code></pre>
|
||
<p>Imports the <code>Human</code> species from <code>schema/beings.sb</code>.</p>
|
||
<h3 id="grouped-import"><a class="header" href="#grouped-import">Grouped Import</a></h3>
|
||
<pre><code class="language-storybook">use schema::core_enums::{Size, EmotionalState};
|
||
</code></pre>
|
||
<p>Imports multiple items from the same module.</p>
|
||
<h3 id="wildcard-import"><a class="header" href="#wildcard-import">Wildcard Import</a></h3>
|
||
<pre><code class="language-storybook">use schema::beings::*;
|
||
</code></pre>
|
||
<p>Imports all public items from <code>schema/beings.sb</code>.</p>
|
||
<h3 id="qualified-paths-1"><a class="header" href="#qualified-paths-1">Qualified Paths</a></h3>
|
||
<p><strong>Module structure:</strong></p>
|
||
<pre><code>examples/alice-in-wonderland/
|
||
├─ schema/
|
||
│ ├─ core_enums.sb
|
||
│ ├─ templates.sb
|
||
│ └─ beings.sb
|
||
└─ world/
|
||
├─ characters/
|
||
│ └─ alice.sb
|
||
└─ locations/
|
||
└─ wonderland_places.sb
|
||
</code></pre>
|
||
<p><strong>In <code>martha.sb</code>:</strong></p>
|
||
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Specialty};
|
||
use schema::templates::Baker;
|
||
use schema::beings::Human;
|
||
|
||
character Martha: Human from Baker {
|
||
skill_level: master
|
||
specialty: sourdough
|
||
}
|
||
</code></pre>
|
||
<h3 id="path-resolution"><a class="header" href="#path-resolution">Path Resolution</a></h3>
|
||
<ol>
|
||
<li><strong>Relative to file’s module path</strong>: Imports are resolved relative to the current file’s location</li>
|
||
<li><strong>Module hierarchy</strong>: <code>schema::beings</code> maps to <code>schema/beings.sb</code></li>
|
||
<li><strong>Transitive imports</strong>: Imported modules can themselves import others</li>
|
||
</ol>
|
||
<h3 id="common-patterns-1"><a class="header" href="#common-patterns-1">Common Patterns</a></h3>
|
||
<p><strong>Schema organization:</strong></p>
|
||
<pre><code class="language-storybook">// In schema/core_enums.sb
|
||
enum SkillLevel { novice, beginner, intermediate, advanced, master }
|
||
enum Specialty { sourdough, pastries, cakes, general }
|
||
|
||
// In world/characters/martha.sb
|
||
use schema::core_enums::{SkillLevel, Specialty};
|
||
</code></pre>
|
||
<p><strong>Template composition:</strong></p>
|
||
<pre><code class="language-storybook">// In schema/templates.sb
|
||
template SkilledWorker { ... }
|
||
template Baker {
|
||
include SkilledWorker
|
||
...
|
||
}
|
||
|
||
// In world/characters/martha.sb
|
||
use schema::templates::Baker;
|
||
</code></pre>
|
||
<p><strong>Cross-file references:</strong></p>
|
||
<pre><code class="language-storybook">// In world/characters/martha.sb
|
||
character Martha { ... }
|
||
|
||
// In world/relationships/bakery.sb
|
||
use world::characters::Martha;
|
||
|
||
relationship MarthaAndJane {
|
||
Martha
|
||
Jane
|
||
}
|
||
</code></pre>
|
||
<h3 id="validation-rules-13"><a class="header" href="#validation-rules-13">Validation Rules</a></h3>
|
||
<ol>
|
||
<li><strong>Path exists</strong>: Imported paths must reference defined modules/items</li>
|
||
<li><strong>No circular imports</strong>: Modules cannot form circular dependency chains</li>
|
||
<li><strong>Valid identifiers</strong>: All path segments must be valid identifiers</li>
|
||
<li><strong>Grouped import validity</strong>: All items in <code>{}</code> must exist</li>
|
||
</ol>
|
||
<h3 id="best-practices-5"><a class="header" href="#best-practices-5">Best Practices</a></h3>
|
||
<p><strong>1. Group related imports:</strong></p>
|
||
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Specialty, Confidence};
|
||
use schema::beings::{Human, Cat};
|
||
</code></pre>
|
||
<p><strong>2. Explicit over wildcard:</strong></p>
|
||
<pre><code class="language-storybook">// Prefer:
|
||
use schema::core_enums::{SkillLevel, Specialty};
|
||
|
||
// Over:
|
||
use schema::core_enums::*; // Imports everything
|
||
</code></pre>
|
||
<p><strong>3. Organize by hierarchy:</strong></p>
|
||
<pre><code class="language-storybook">// Schema imports first
|
||
use schema::core_enums::SkillLevel;
|
||
use schema::templates::Baker;
|
||
|
||
// Then world imports
|
||
use world::characters::Martha;
|
||
</code></pre>
|
||
<p><strong>4. Use qualified paths for clarity:</strong></p>
|
||
<pre><code class="language-storybook">character Elena: Human from Baker, Apprentice {
|
||
// Human is species (from schema::beings)
|
||
// Baker, Apprentice are templates (from schema::templates)
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="cross-references-8"><a class="header" href="#cross-references-8">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> - Using templates and species</li>
|
||
<li><a href="reference/./15-relationships.html">Relationships</a> - Institutions can participate</li>
|
||
<li><a href="reference/./14-schedules.html">Schedules</a> - Resource linking</li>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> - Resource linking</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - Field value types</li>
|
||
</ul>
|
||
<h2 id="related-concepts-6"><a class="header" href="#related-concepts-6">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Modularity</strong>: Use statements enable file organization</li>
|
||
<li><strong>Composition</strong>: Templates provide trait-based composition</li>
|
||
<li><strong>Type safety</strong>: Enums prevent invalid values</li>
|
||
<li><strong>World-building</strong>: Locations and institutions model environments</li>
|
||
<li><strong>Ontology</strong>: Species define entity essence</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="expression-language-1"><a class="header" href="#expression-language-1">Expression Language</a></h1>
|
||
<p>The Storybook expression language enables conditions, queries, and logic throughout the system. Expressions appear in life arc transitions, behavior tree guards, decorator conditions, and relationship queries. This chapter provides a complete reference for expression syntax and semantics.</p>
|
||
<h2 id="what-are-expressions"><a class="header" href="#what-are-expressions">What are Expressions?</a></h2>
|
||
<p>Expressions are logical statements that evaluate to <code>true</code> or <code>false</code>. They combine:</p>
|
||
<ul>
|
||
<li><strong>Literals</strong>: Numbers, strings, booleans</li>
|
||
<li><strong>Identifiers</strong>: References to fields and entities</li>
|
||
<li><strong>Comparisons</strong>: <code>==</code>, <code>!=</code>, <code><</code>, <code><=</code>, <code>></code>, <code>>=</code></li>
|
||
<li><strong>Logical operators</strong>: <code>and</code>, <code>or</code>, <code>not</code></li>
|
||
<li><strong>Field access</strong>: <code>self.field</code>, <code>other.field</code></li>
|
||
<li><strong>Quantifiers</strong>: <code>forall</code>, <code>exists</code> (for collections)</li>
|
||
</ul>
|
||
<h2 id="syntax-21"><a class="header" href="#syntax-21">Syntax</a></h2>
|
||
<pre><code class="language-bnf"><expression> ::= <literal>
|
||
| <identifier>
|
||
| <field-access>
|
||
| <comparison>
|
||
| <logical>
|
||
| <unary>
|
||
| <quantifier>
|
||
| "(" <expression> ")"
|
||
|
||
<literal> ::= <int> | <float> | <string> | <bool>
|
||
|
||
<identifier> ::= <simple-name>
|
||
| <qualified-path>
|
||
|
||
<field-access> ::= <expression> "." <identifier>
|
||
| "self" "." <identifier>
|
||
| "other" "." <identifier>
|
||
|
||
<comparison> ::= <expression> <comp-op> <expression>
|
||
|
||
<comp-op> ::= "==" | "!=" | "<" | "<=" | ">" | ">="
|
||
|
||
<logical> ::= <expression> "and" <expression>
|
||
| <expression> "or" <expression>
|
||
|
||
<unary> ::= "not" <expression>
|
||
| "-" <expression>
|
||
|
||
<quantifier> ::= ("forall" | "exists") <identifier> "in" <expression> ":" <expression>
|
||
</code></pre>
|
||
<h2 id="literals"><a class="header" href="#literals">Literals</a></h2>
|
||
<h3 id="integer-literals"><a class="header" href="#integer-literals">Integer Literals</a></h3>
|
||
<pre><code class="language-storybook">42
|
||
-7
|
||
0
|
||
1000
|
||
</code></pre>
|
||
<h3 id="float-literals"><a class="header" href="#float-literals">Float Literals</a></h3>
|
||
<pre><code class="language-storybook">3.14
|
||
-0.5
|
||
0.0
|
||
100.25
|
||
</code></pre>
|
||
<h3 id="string-literals"><a class="header" href="#string-literals">String Literals</a></h3>
|
||
<pre><code class="language-storybook">"Martha"
|
||
"Sourdough takes patience."
|
||
"active"
|
||
</code></pre>
|
||
<p>Strings are enclosed in double quotes. Escape sequences: <code>\n</code>, <code>\t</code>, <code>\\</code>, <code>\"</code>.</p>
|
||
<h3 id="boolean-literals"><a class="header" href="#boolean-literals">Boolean Literals</a></h3>
|
||
<pre><code class="language-storybook">true
|
||
false
|
||
</code></pre>
|
||
<h2 id="identifiers"><a class="header" href="#identifiers">Identifiers</a></h2>
|
||
<p>Identifiers reference fields or entities.</p>
|
||
<h3 id="simple-identifiers"><a class="header" href="#simple-identifiers">Simple Identifiers</a></h3>
|
||
<pre><code class="language-storybook">health
|
||
enemy_count
|
||
is_ready
|
||
</code></pre>
|
||
<h3 id="qualified-paths-2"><a class="header" href="#qualified-paths-2">Qualified Paths</a></h3>
|
||
<pre><code class="language-storybook">Martha.skill_level
|
||
Character.emotional_state
|
||
</code></pre>
|
||
<h2 id="comparison-operators"><a class="header" href="#comparison-operators">Comparison Operators</a></h2>
|
||
<h3 id="equality-"><a class="header" href="#equality-">Equality: <code>==</code></a></h3>
|
||
<p>Tests if two values are equal.</p>
|
||
<pre><code class="language-storybook">name == "Martha"
|
||
count == 5
|
||
status == active
|
||
</code></pre>
|
||
<p><strong>Type compatibility:</strong></p>
|
||
<ul>
|
||
<li>Both operands must be the same type</li>
|
||
<li>Works with: int, float, string, bool, enum values</li>
|
||
</ul>
|
||
<h3 id="inequality-"><a class="header" href="#inequality-">Inequality: <code>!=</code></a></h3>
|
||
<p>Tests if two values are not equal.</p>
|
||
<pre><code class="language-storybook">name != "Gregory"
|
||
health != 0
|
||
ready != true
|
||
</code></pre>
|
||
<h3 id="less-than"><a class="header" href="#less-than">Less Than: <code><</code></a></h3>
|
||
<p>Tests if left operand is less than right.</p>
|
||
<pre><code class="language-storybook">health < 20
|
||
age < 18
|
||
distance < 10.0
|
||
</code></pre>
|
||
<p><strong>Valid types:</strong> int, float</p>
|
||
<h3 id="less-than-or-equal-"><a class="header" href="#less-than-or-equal-">Less Than or Equal: <code><=</code></a></h3>
|
||
<pre><code class="language-storybook">health <= 50
|
||
count <= max_count
|
||
</code></pre>
|
||
<h3 id="greater-than"><a class="header" href="#greater-than">Greater Than: <code>></code></a></h3>
|
||
<pre><code class="language-storybook">strength > 10
|
||
bond > 0.5
|
||
</code></pre>
|
||
<h3 id="greater-than-or-equal-"><a class="header" href="#greater-than-or-equal-">Greater Than or Equal: <code>>=</code></a></h3>
|
||
<pre><code class="language-storybook">age >= 21
|
||
score >= 100
|
||
</code></pre>
|
||
<h2 id="logical-operators"><a class="header" href="#logical-operators">Logical Operators</a></h2>
|
||
<h3 id="and-and"><a class="header" href="#and-and">AND: <code>and</code></a></h3>
|
||
<p>Both operands must be true.</p>
|
||
<pre><code class="language-storybook">health < 50 and has_potion
|
||
is_ready and not is_busy
|
||
age >= 18 and age < 65
|
||
</code></pre>
|
||
<p><strong>Evaluation:</strong> Short-circuit (if left is false, right is not evaluated)</p>
|
||
<h3 id="or-or"><a class="header" href="#or-or">OR: <code>or</code></a></h3>
|
||
<p>At least one operand must be true.</p>
|
||
<pre><code class="language-storybook">is_day or is_lit
|
||
health < 20 or surrounded
|
||
enemy_count == 0 or all_enemies_dead
|
||
</code></pre>
|
||
<p><strong>Evaluation:</strong> Short-circuit (if left is true, right is not evaluated)</p>
|
||
<h3 id="operator-precedence"><a class="header" href="#operator-precedence">Operator Precedence</a></h3>
|
||
<p>From highest to lowest:</p>
|
||
<ol>
|
||
<li><strong>Parentheses</strong>: <code>(...)</code></li>
|
||
<li><strong>Unary</strong>: <code>not</code>, <code>-</code></li>
|
||
<li><strong>Comparisons</strong>: <code>==</code>, <code>!=</code>, <code><</code>, <code><=</code>, <code>></code>, <code>>=</code></li>
|
||
<li><strong>AND</strong>: <code>and</code></li>
|
||
<li><strong>OR</strong>: <code>or</code></li>
|
||
</ol>
|
||
<p><strong>Examples:</strong></p>
|
||
<pre><code class="language-storybook">not is_ready and is_awake
|
||
// Equivalent to: (not is_ready) and is_awake
|
||
|
||
health < 50 or is_poisoned and has_antidote
|
||
// Equivalent to: (health < 50) or (is_poisoned and has_antidote)
|
||
|
||
// Use parentheses for clarity:
|
||
(health < 50 or is_poisoned) and has_antidote
|
||
</code></pre>
|
||
<h2 id="unary-operators"><a class="header" href="#unary-operators">Unary Operators</a></h2>
|
||
<h3 id="not-not"><a class="header" href="#not-not">NOT: <code>not</code></a></h3>
|
||
<p>Inverts a boolean value.</p>
|
||
<pre><code class="language-storybook">not is_ready
|
||
not (health < 20)
|
||
not enemy_nearby and safe
|
||
</code></pre>
|
||
<h3 id="negation--"><a class="header" href="#negation--">Negation: <code>-</code></a></h3>
|
||
<p>Negates a numeric value.</p>
|
||
<pre><code class="language-storybook">-health
|
||
-10
|
||
-(max_value - current_value)
|
||
</code></pre>
|
||
<h2 id="field-access"><a class="header" href="#field-access">Field Access</a></h2>
|
||
<h3 id="direct-field-access"><a class="header" href="#direct-field-access">Direct Field Access</a></h3>
|
||
<pre><code class="language-storybook">health
|
||
bond
|
||
emotional_state
|
||
</code></pre>
|
||
<p>References a field on the current entity.</p>
|
||
<h3 id="dot-access"><a class="header" href="#dot-access">Dot Access</a></h3>
|
||
<pre><code class="language-storybook">Martha.skill_level
|
||
Character.emotional_state
|
||
enemy.health
|
||
</code></pre>
|
||
<p>Access fields on other entities.</p>
|
||
<h3 id="self-access"><a class="header" href="#self-access">Self Access</a></h3>
|
||
<p>In relationships and certain contexts, <code>self</code> refers to the current participant:</p>
|
||
<pre><code class="language-storybook">self.bond
|
||
self.responsibility
|
||
self.trust
|
||
</code></pre>
|
||
<p><strong>Use case:</strong> Relationship transitions, symmetric queries</p>
|
||
<h3 id="other-access"><a class="header" href="#other-access">Other Access</a></h3>
|
||
<p>In relationships, <code>other</code> refers to other participants:</p>
|
||
<pre><code class="language-storybook">other.bond
|
||
other.aware_of_mentor
|
||
other.respect
|
||
</code></pre>
|
||
<p><strong>Use case:</strong> Relationship queries with perspective</p>
|
||
<h3 id="example-in-life-arcs"><a class="header" href="#example-in-life-arcs">Example in Life Arcs</a></h3>
|
||
<pre><code class="language-storybook">life_arc RelationshipState {
|
||
state new {
|
||
on self.bond > 0.7 and other.bond > 0.7 -> stable
|
||
}
|
||
|
||
state stable {
|
||
on self.bond < 0.3 -> troubled
|
||
on other.bond < 0.3 -> troubled
|
||
}
|
||
|
||
state troubled {
|
||
on self.bond < 0.1 or other.bond < 0.1 -> broken
|
||
}
|
||
|
||
state broken {}
|
||
}
|
||
</code></pre>
|
||
<h2 id="quantifiers"><a class="header" href="#quantifiers">Quantifiers</a></h2>
|
||
<p>Quantifiers test conditions over collections.</p>
|
||
<h3 id="forall-forall"><a class="header" href="#forall-forall">ForAll: <code>forall</code></a></h3>
|
||
<p>Tests if a condition holds for all elements in a collection.</p>
|
||
<pre><code class="language-storybook">forall e in enemies: e.defeated
|
||
forall item in inventory: item.weight < 10
|
||
</code></pre>
|
||
<p><strong>Syntax:</strong></p>
|
||
<pre><code class="language-bnf">forall <variable> in <collection>: <predicate>
|
||
</code></pre>
|
||
<p><strong>Semantics:</strong></p>
|
||
<ul>
|
||
<li>Returns <code>true</code> if predicate is true for every element</li>
|
||
<li>Returns <code>true</code> for empty collections (vacuously true)</li>
|
||
</ul>
|
||
<p><strong>Examples:</strong></p>
|
||
<pre><code class="language-storybook">// All enemies defeated?
|
||
forall enemy in enemies: enemy.health <= 0
|
||
|
||
// All party members ready?
|
||
forall member in party: member.is_ready
|
||
|
||
// All doors locked?
|
||
forall door in doors: door.is_locked
|
||
</code></pre>
|
||
<h3 id="exists-exists"><a class="header" href="#exists-exists">Exists: <code>exists</code></a></h3>
|
||
<p>Tests if a condition holds for at least one element.</p>
|
||
<pre><code class="language-storybook">exists e in enemies: e.is_hostile
|
||
exists item in inventory: item.is_healing_potion
|
||
</code></pre>
|
||
<p><strong>Syntax:</strong></p>
|
||
<pre><code class="language-bnf">exists <variable> in <collection>: <predicate>
|
||
</code></pre>
|
||
<p><strong>Semantics:</strong></p>
|
||
<ul>
|
||
<li>Returns <code>true</code> if predicate is true for any element</li>
|
||
<li>Returns <code>false</code> for empty collections</li>
|
||
</ul>
|
||
<p><strong>Examples:</strong></p>
|
||
<pre><code class="language-storybook">// Any enemy nearby?
|
||
exists enemy in enemies: enemy.distance < 10
|
||
|
||
// Any door unlocked?
|
||
exists door in doors: not door.is_locked
|
||
|
||
// Any ally wounded?
|
||
exists ally in allies: ally.health < ally.max_health * 0.5
|
||
</code></pre>
|
||
<h3 id="nested-quantifiers"><a class="header" href="#nested-quantifiers">Nested Quantifiers</a></h3>
|
||
<p>Quantifiers can nest:</p>
|
||
<pre><code class="language-storybook">forall team in teams: exists player in team: player.is_leader
|
||
// Every team has at least one leader
|
||
|
||
exists room in dungeon: forall enemy in room.enemies: enemy.defeated
|
||
// At least one room has all enemies defeated
|
||
</code></pre>
|
||
<h2 id="usage-in-context"><a class="header" href="#usage-in-context">Usage in Context</a></h2>
|
||
<h3 id="life-arc-transitions"><a class="header" href="#life-arc-transitions">Life Arc Transitions</a></h3>
|
||
<pre><code class="language-storybook">life_arc CombatState {
|
||
state idle {
|
||
on enemy_count > 0 -> combat
|
||
}
|
||
|
||
state combat {
|
||
on health < 20 -> fleeing
|
||
on enemy_count == 0 -> victorious
|
||
}
|
||
|
||
state fleeing {
|
||
on distance_from_enemies > 100 -> safe
|
||
}
|
||
|
||
state victorious {
|
||
on celebration_complete -> idle
|
||
}
|
||
|
||
state safe {
|
||
on health >= 50 -> idle
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="behavior-tree-conditions"><a class="header" href="#behavior-tree-conditions">Behavior Tree Conditions</a></h3>
|
||
<pre><code class="language-storybook">behavior GuardedAction {
|
||
if(health > 50 and has_weapon) {
|
||
AggressiveAttack
|
||
}
|
||
}
|
||
|
||
behavior ConditionalChoice {
|
||
choose tactics {
|
||
then melee {
|
||
if(distance < 5 and weapon_type == "sword")
|
||
MeleeAttack
|
||
}
|
||
|
||
then ranged {
|
||
if(distance >= 5 and has_arrows)
|
||
RangedAttack
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="behavior-tree-conditions-1"><a class="header" href="#behavior-tree-conditions-1">Behavior Tree Conditions</a></h3>
|
||
<pre><code class="language-storybook">behavior SmartAI {
|
||
choose strategy {
|
||
then aggressive {
|
||
if(health > 70 and enemy_count < 3)
|
||
Attack
|
||
}
|
||
|
||
then defensive {
|
||
if(health < 30 or enemy_count >= 5)
|
||
Defend
|
||
}
|
||
|
||
then balanced {
|
||
if(health >= 30 and health <= 70)
|
||
TacticalManeuver
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="type-system"><a class="header" href="#type-system">Type System</a></h2>
|
||
<h3 id="type-compatibility"><a class="header" href="#type-compatibility">Type Compatibility</a></h3>
|
||
<p>Comparisons require compatible types:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Operator</th><th>Left Type</th><th>Right Type</th><th>Valid?</th></tr></thead><tbody>
|
||
<tr><td><code>==</code>, <code>!=</code></td><td>int</td><td>int</td><td>✓</td></tr>
|
||
<tr><td><code>==</code>, <code>!=</code></td><td>float</td><td>float</td><td>✓</td></tr>
|
||
<tr><td><code>==</code>, <code>!=</code></td><td>string</td><td>string</td><td>✓</td></tr>
|
||
<tr><td><code>==</code>, <code>!=</code></td><td>bool</td><td>bool</td><td>✓</td></tr>
|
||
<tr><td><code>==</code>, <code>!=</code></td><td>enum</td><td>same enum</td><td>✓</td></tr>
|
||
<tr><td><code>==</code>, <code>!=</code></td><td>int</td><td>float</td><td>✗</td></tr>
|
||
<tr><td><code><</code>, <code><=</code>, <code>></code>, <code>>=</code></td><td>int</td><td>int</td><td>✓</td></tr>
|
||
<tr><td><code><</code>, <code><=</code>, <code>></code>, <code>>=</code></td><td>float</td><td>float</td><td>✓</td></tr>
|
||
<tr><td><code><</code>, <code><=</code>, <code>></code>, <code>>=</code></td><td>string</td><td>string</td><td>✗</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="implicit-coercion"><a class="header" href="#implicit-coercion">Implicit Coercion</a></h3>
|
||
<p><strong>None.</strong> Storybook has no implicit type coercion. All comparisons must be between compatible types.</p>
|
||
<p><strong>Error:</strong></p>
|
||
<pre><code class="language-storybook">count == "5" // Error: int vs string
|
||
health < true // Error: int vs bool
|
||
</code></pre>
|
||
<p><strong>Correct:</strong></p>
|
||
<pre><code class="language-storybook">count == 5
|
||
health < 50
|
||
</code></pre>
|
||
<h2 id="special-keyword-is"><a class="header" href="#special-keyword-is">Special Keyword: <code>is</code></a></h2>
|
||
<p>The <code>is</code> keyword provides syntactic sugar for equality with enum values:</p>
|
||
<pre><code class="language-storybook">// Instead of:
|
||
status == active
|
||
|
||
// You can write:
|
||
status is active
|
||
</code></pre>
|
||
<p><strong>More examples:</strong></p>
|
||
<pre><code class="language-storybook">name is "Martha"
|
||
skill_level is master
|
||
emotional_state is focused
|
||
</code></pre>
|
||
<p>This is purely syntactic—<code>is</code> and <code>==</code> are equivalent.</p>
|
||
<h2 id="complete-examples-4"><a class="header" href="#complete-examples-4">Complete Examples</a></h2>
|
||
<h3 id="simple-conditions"><a class="header" href="#simple-conditions">Simple Conditions</a></h3>
|
||
<pre><code class="language-storybook">health < 20
|
||
enemy_nearby
|
||
not is_ready
|
||
count > 5
|
||
</code></pre>
|
||
<h3 id="complex-conditions"><a class="header" href="#complex-conditions">Complex Conditions</a></h3>
|
||
<pre><code class="language-storybook">(health < 20 and not has_potion) or surrounded
|
||
forall e in enemies: e.defeated
|
||
exists item in inventory: item.is_healing_potion and item.quantity > 0
|
||
</code></pre>
|
||
<h3 id="life-arc-with-complex-conditions"><a class="header" href="#life-arc-with-complex-conditions">Life Arc with Complex Conditions</a></h3>
|
||
<pre><code class="language-storybook">life_arc CharacterMood {
|
||
state content {
|
||
on health < 30 or hunger > 80 -> distressed
|
||
on social_interaction > 0.8 -> happy
|
||
}
|
||
|
||
state distressed {
|
||
on health >= 50 and hunger < 30 -> content
|
||
on (health < 10 or hunger > 95) and help_available -> desperate
|
||
}
|
||
|
||
state happy {
|
||
on social_interaction < 0.3 -> content
|
||
on received_bad_news -> distressed
|
||
}
|
||
|
||
state desperate {
|
||
on help_received -> distressed
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="behavior-with-quantifiers"><a class="header" href="#behavior-with-quantifiers">Behavior with Quantifiers</a></h3>
|
||
<pre><code class="language-storybook">behavior SquadLeader {
|
||
choose leadership {
|
||
then regroup {
|
||
if(squad_has_wounded)
|
||
OrderRetreat
|
||
}
|
||
|
||
then advance {
|
||
if(squad_all_ready)
|
||
OrderAdvance
|
||
}
|
||
|
||
then hold_position {
|
||
if(not squad_all_ready)
|
||
OrderHold
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="relationship-query"><a class="header" href="#relationship-query">Relationship Query</a></h3>
|
||
<pre><code class="language-storybook">life_arc FriendshipQuality {
|
||
state new_friends {
|
||
on self.bond > 0.7 and other.bond > 0.7 -> strong_bond
|
||
on self.trust < 0.3 or other.trust < 0.3 -> shaky
|
||
}
|
||
|
||
state strong_bond {
|
||
on self.bond < 0.5 -> weakening
|
||
}
|
||
|
||
state weakening {
|
||
on self.bond < 0.2 or other.bond < 0.2 -> ended
|
||
on self.bond > 0.7 and other.bond > 0.7 -> strong_bond
|
||
}
|
||
|
||
state shaky {
|
||
on self.trust > 0.6 and other.trust > 0.6 -> new_friends
|
||
on self.trust < 0.1 or other.trust < 0.1 -> ended
|
||
}
|
||
|
||
state ended {}
|
||
}
|
||
</code></pre>
|
||
<h2 id="validation-rules-14"><a class="header" href="#validation-rules-14">Validation Rules</a></h2>
|
||
<ol>
|
||
<li><strong>Type consistency</strong>: Both sides of comparison must be compatible types</li>
|
||
<li><strong>Boolean context</strong>: Logical operators (<code>and</code>, <code>or</code>, <code>not</code>) require boolean operands</li>
|
||
<li><strong>Field existence</strong>: Referenced fields must exist on the entity</li>
|
||
<li><strong>Collection validity</strong>: Quantifiers require collection-typed expressions</li>
|
||
<li><strong>Variable scope</strong>: Quantifier variables only valid within their predicate</li>
|
||
<li><strong>No division by zero</strong>: Arithmetic operations must not divide by zero</li>
|
||
<li><strong>Enum validity</strong>: Enum comparisons must reference defined enum values</li>
|
||
</ol>
|
||
<h2 id="best-practices-6"><a class="header" href="#best-practices-6">Best Practices</a></h2>
|
||
<h3 id="1-use-parentheses-for-clarity"><a class="header" href="#1-use-parentheses-for-clarity">1. Use Parentheses for Clarity</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">health < 50 or is_poisoned and has_antidote
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">(health < 50 or is_poisoned) and has_antidote
|
||
</code></pre>
|
||
<h3 id="2-break-complex-conditions"><a class="header" href="#2-break-complex-conditions">2. Break Complex Conditions</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">on (health < 20 and not has_potion) or (surrounded and not has_escape) or (enemy_count > 10 and weapon_broken) -> desperate
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">state combat {
|
||
on health < 20 and not has_potion -> desperate
|
||
on surrounded and not has_escape -> desperate
|
||
on enemy_count > 10 and weapon_broken -> desperate
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-name-complex-conditions"><a class="header" href="#3-name-complex-conditions">3. Name Complex Conditions</a></h3>
|
||
<p>For repeated complex conditions, consider using intermediate fields:</p>
|
||
<p><strong>Instead of:</strong></p>
|
||
<pre><code class="language-storybook">on health < (max_health * 0.2) and enemy_count > 5 -> flee
|
||
</code></pre>
|
||
<p><strong>Consider:</strong></p>
|
||
<pre><code class="language-storybook">// In character definition:
|
||
critically_wounded: health < (max_health * 0.2)
|
||
outnumbered: enemy_count > 5
|
||
|
||
// In life arc:
|
||
on critically_wounded and outnumbered -> flee
|
||
</code></pre>
|
||
<h3 id="4-use-is-for-enums"><a class="header" href="#4-use-is-for-enums">4. Use <code>is</code> for Enums</a></h3>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">status is active
|
||
emotional_state is focused
|
||
</code></pre>
|
||
<p><strong>Over:</strong></p>
|
||
<pre><code class="language-storybook">status == active
|
||
emotional_state == focused
|
||
</code></pre>
|
||
<h3 id="5-quantifiers-for-collections"><a class="header" href="#5-quantifiers-for-collections">5. Quantifiers for Collections</a></h3>
|
||
<p><strong>Avoid:</strong></p>
|
||
<pre><code class="language-storybook">// Manual checks for each element
|
||
if enemy1.defeated and enemy2.defeated and enemy3.defeated
|
||
</code></pre>
|
||
<p><strong>Prefer:</strong></p>
|
||
<pre><code class="language-storybook">if forall enemy in enemies: enemy.defeated
|
||
</code></pre>
|
||
<h2 id="cross-references-9"><a class="header" href="#cross-references-9">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./13-life-arcs.html">Life Arcs</a> - Transition conditions</li>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> - Guard and condition nodes</li>
|
||
<li><a href="reference/./12-decorators.html">Decorators</a> - Guard decorator</li>
|
||
<li><a href="reference/./15-relationships.html">Relationships</a> - Self/other field access</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - Literal value types</li>
|
||
</ul>
|
||
<h2 id="related-concepts-7"><a class="header" href="#related-concepts-7">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Type safety</strong>: Strong typing prevents type errors at compile time</li>
|
||
<li><strong>Short-circuit evaluation</strong>: AND/OR operators optimize evaluation</li>
|
||
<li><strong>Quantifiers</strong>: Enable expressive collection queries</li>
|
||
<li><strong>Field access</strong>: Context-sensitive (<code>self</code>, <code>other</code>) for relationships</li>
|
||
<li><strong>Boolean algebra</strong>: Standard logical operators with expected semantics</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="values"><a class="header" href="#values">Values</a></h1>
|
||
<p>Values are the fundamental data types in Storybook. Every field in a character, template, or other declaration contains a value. This chapter provides a complete reference for all supported value types.</p>
|
||
<h2 id="value-types-overview"><a class="header" href="#value-types-overview">Value Types Overview</a></h2>
|
||
<p>Storybook supports 12 value types:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Example</th><th>Use Case</th></tr></thead><tbody>
|
||
<tr><td><strong>Int</strong></td><td><code>42</code>, <code>-7</code></td><td>Quantities, IDs, counts</td></tr>
|
||
<tr><td><strong>Float</strong></td><td><code>3.14</code>, <code>-0.5</code></td><td>Measurements, probabilities</td></tr>
|
||
<tr><td><strong>String</strong></td><td><code>"Hello"</code>, <code>"Martha"</code></td><td>Text, names, descriptions</td></tr>
|
||
<tr><td><strong>Bool</strong></td><td><code>true</code>, <code>false</code></td><td>Flags, switches</td></tr>
|
||
<tr><td><strong>Time</strong></td><td><code>14:30</code>, <code>09:15:30</code></td><td>Clock times, schedule blocks</td></tr>
|
||
<tr><td><strong>Duration</strong></td><td><code>2h30m</code>, <code>45s</code></td><td>Time intervals</td></tr>
|
||
<tr><td><strong>Range</strong></td><td><code>20..40</code>, <code>0.5..1.0</code></td><td>Template variation bounds</td></tr>
|
||
<tr><td><strong>Identifier</strong></td><td><code>Martha</code>, <code>items::sword</code></td><td>References to other declarations</td></tr>
|
||
<tr><td><strong>List</strong></td><td><code>[1, 2, 3]</code>, <code>["a", "b"]</code></td><td>Collections</td></tr>
|
||
<tr><td><strong>Object</strong></td><td><code>{x: 10, y: 20}</code></td><td>Structured data</td></tr>
|
||
<tr><td><strong>ProseBlock</strong></td><td><code>---tag content ---</code></td><td>Long-form narrative</td></tr>
|
||
<tr><td><strong>Override</strong></td><td><code>TemplateX with {...}</code></td><td>Template instantiation with modifications</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="integer"><a class="header" href="#integer">Integer</a></h2>
|
||
<p>Signed 64-bit integers.</p>
|
||
<h3 id="syntax-22"><a class="header" href="#syntax-22">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><int> ::= ["-"] <digit>+
|
||
<digit> ::= "0".."9"
|
||
</code></pre>
|
||
<h3 id="examples-6"><a class="header" href="#examples-6">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Hero {
|
||
age: 25
|
||
gold: 1500
|
||
reputation: -10
|
||
}
|
||
</code></pre>
|
||
<h3 id="range"><a class="header" href="#range">Range</a></h3>
|
||
<ul>
|
||
<li>Minimum: <code>-9,223,372,036,854,775,808</code></li>
|
||
<li>Maximum: <code>9,223,372,036,854,775,807</code></li>
|
||
</ul>
|
||
<h3 id="use-cases-6"><a class="header" href="#use-cases-6">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Counts (items, population)</li>
|
||
<li>Identifiers (IDs, keys)</li>
|
||
<li>Scores (reputation, alignment)</li>
|
||
<li>Whole quantities (gold pieces, HP)</li>
|
||
</ul>
|
||
<h2 id="float"><a class="header" href="#float">Float</a></h2>
|
||
<p>64-bit floating-point numbers (IEEE 754 double precision).</p>
|
||
<h3 id="syntax-23"><a class="header" href="#syntax-23">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><float> ::= ["-"] <digit>+ "." <digit>+
|
||
| ["-"] <digit>+ ("e" | "E") ["+"|"-"] <digit>+
|
||
</code></pre>
|
||
<h3 id="examples-7"><a class="header" href="#examples-7">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Wizard {
|
||
mana: 100.0
|
||
spell_power: 1.5
|
||
corruption: 0.03
|
||
precision: 1e-6
|
||
}
|
||
</code></pre>
|
||
<h3 id="special-values"><a class="header" href="#special-values">Special Values</a></h3>
|
||
<ul>
|
||
<li>No <code>NaN</code> or <code>Infinity</code> support (use validation to prevent)</li>
|
||
<li>Negative zero (<code>-0.0</code>) equals positive zero (<code>0.0</code>)</li>
|
||
</ul>
|
||
<h3 id="use-cases-7"><a class="header" href="#use-cases-7">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Probabilities (0.0 to 1.0)</li>
|
||
<li>Percentages (0.0 to 100.0)</li>
|
||
<li>Measurements with precision</li>
|
||
<li>Ratios and scaling factors</li>
|
||
</ul>
|
||
<h2 id="string"><a class="header" href="#string">String</a></h2>
|
||
<p>UTF-8 encoded text enclosed in double quotes.</p>
|
||
<h3 id="syntax-24"><a class="header" href="#syntax-24">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><string> ::= '"' <string-char>* '"'
|
||
<string-char> ::= <any-char-except-quote-or-backslash>
|
||
| <escape-sequence>
|
||
<escape-sequence> ::= "\n" | "\r" | "\t" | "\\" | "\""
|
||
</code></pre>
|
||
<h3 id="escape-sequences"><a class="header" href="#escape-sequences">Escape Sequences</a></h3>
|
||
<ul>
|
||
<li><code>\n</code> - Newline</li>
|
||
<li><code>\r</code> - Carriage return</li>
|
||
<li><code>\t</code> - Tab</li>
|
||
<li><code>\\</code> - Backslash</li>
|
||
<li><code>\"</code> - Double quote</li>
|
||
</ul>
|
||
<h3 id="examples-8"><a class="header" href="#examples-8">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Martha {
|
||
name: "Martha Baker"
|
||
greeting: "Fresh from the oven!"
|
||
multiline: "Line 1\nLine 2\nLine 3"
|
||
quote: "She said, \"The bread is ready!\""
|
||
}
|
||
</code></pre>
|
||
<h3 id="limitations"><a class="header" href="#limitations">Limitations</a></h3>
|
||
<ul>
|
||
<li>No raw strings (r“…“) or multi-line literals</li>
|
||
<li>For long text, use <a href="reference/18-value-types.html#prose-blocks">prose blocks</a></li>
|
||
<li>Maximum length: Implementation-defined (typically several MB)</li>
|
||
</ul>
|
||
<h3 id="use-cases-8"><a class="header" href="#use-cases-8">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Character names</li>
|
||
<li>Short descriptions</li>
|
||
<li>Dialogue snippets</li>
|
||
<li>Enum-like values (before proper enums)</li>
|
||
</ul>
|
||
<h2 id="boolean"><a class="header" href="#boolean">Boolean</a></h2>
|
||
<p>Logical true/false values.</p>
|
||
<h3 id="syntax-25"><a class="header" href="#syntax-25">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><bool> ::= "true" | "false"
|
||
</code></pre>
|
||
<h3 id="examples-9"><a class="header" href="#examples-9">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Guard {
|
||
is_awake: true
|
||
has_seen_player: false
|
||
loyal_to_king: true
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-cases-9"><a class="header" href="#use-cases-9">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Feature flags (can_fly, is_hostile)</li>
|
||
<li>State tracking (door_open, quest_complete)</li>
|
||
<li>Conditions (is_friendly, accepts_bribes)</li>
|
||
</ul>
|
||
<h2 id="time"><a class="header" href="#time">Time</a></h2>
|
||
<p>Clock time in 24-hour format.</p>
|
||
<h3 id="syntax-26"><a class="header" href="#syntax-26">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><time> ::= <hour> ":" <minute>
|
||
| <hour> ":" <minute> ":" <second>
|
||
|
||
<hour> ::= "0".."23"
|
||
<minute> ::= "0".."59"
|
||
<second> ::= "0".."59"
|
||
</code></pre>
|
||
<h3 id="examples-10"><a class="header" href="#examples-10">Examples</a></h3>
|
||
<pre><code class="language-storybook">schedule BakerySchedule {
|
||
block {
|
||
start: 06:00
|
||
end: 08:30
|
||
action: baking::prepare_dough
|
||
}
|
||
|
||
block {
|
||
start: 14:30:15 // With seconds
|
||
end: 15:00:00
|
||
action: baking::afternoon_cleanup
|
||
}
|
||
}
|
||
|
||
character EarlyRiser {
|
||
wake_time: 05:30
|
||
bedtime: 21:00
|
||
}
|
||
</code></pre>
|
||
<h3 id="validation"><a class="header" href="#validation">Validation</a></h3>
|
||
<ul>
|
||
<li>Hours: 0-23 (24-hour format)</li>
|
||
<li>Minutes: 0-59</li>
|
||
<li>Seconds: 0-59 (optional)</li>
|
||
<li>No AM/PM notation</li>
|
||
<li>No timezone support (context-dependent)</li>
|
||
</ul>
|
||
<h3 id="use-cases-10"><a class="header" href="#use-cases-10">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Schedule blocks (see <a href="reference/./16-schedules.html">Schedules</a>)</li>
|
||
<li>Character routines</li>
|
||
<li>Event timing</li>
|
||
<li>Time-based conditions</li>
|
||
</ul>
|
||
<h2 id="duration"><a class="header" href="#duration">Duration</a></h2>
|
||
<p>Time intervals with hour/minute/second components.</p>
|
||
<h3 id="syntax-27"><a class="header" href="#syntax-27">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><duration> ::= <duration-component>+
|
||
|
||
<duration-component> ::= <number> ("h" | "m" | "s")
|
||
|
||
<number> ::= <digit>+
|
||
</code></pre>
|
||
<h3 id="examples-11"><a class="header" href="#examples-11">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Traveler {
|
||
journey_time: 2h30m
|
||
rest_needed: 8h
|
||
sprint_duration: 45s
|
||
}
|
||
|
||
behavior TimedAction {
|
||
choose {
|
||
timeout(5m) {
|
||
CompleteObjective
|
||
}
|
||
|
||
cooldown(30s) {
|
||
UseSpecialAbility
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="components-1"><a class="header" href="#components-1">Components</a></h3>
|
||
<ul>
|
||
<li><code>h</code> - Hours</li>
|
||
<li><code>m</code> - Minutes</li>
|
||
<li><code>s</code> - Seconds</li>
|
||
</ul>
|
||
<p>Can combine multiple components: <code>1h30m15s</code></p>
|
||
<h3 id="validation-1"><a class="header" href="#validation-1">Validation</a></h3>
|
||
<ul>
|
||
<li>All components non-negative</li>
|
||
<li>No fractional components (<code>1.5h</code> not allowed; use <code>1h30m</code>)</li>
|
||
<li>Can specify same unit multiple times: <code>90m</code> = <code>1h30m</code> (normalized)</li>
|
||
</ul>
|
||
<h3 id="use-cases-11"><a class="header" href="#use-cases-11">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Behavior tree timeouts/cooldowns</li>
|
||
<li>Travel times</li>
|
||
<li>Cooldown periods</li>
|
||
<li>Event durations</li>
|
||
</ul>
|
||
<h2 id="range-1"><a class="header" href="#range-1">Range</a></h2>
|
||
<p>Numeric range with inclusive bounds (for templates only).</p>
|
||
<h3 id="syntax-28"><a class="header" href="#syntax-28">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><range> ::= <value> ".." <value>
|
||
</code></pre>
|
||
<p>Both bounds must be the same type (both int or both float).</p>
|
||
<h3 id="examples-12"><a class="header" href="#examples-12">Examples</a></h3>
|
||
<pre><code class="language-storybook">template Villager {
|
||
age: 18..65
|
||
wealth: 10..100
|
||
height: 150.0..190.0 // Float range
|
||
}
|
||
|
||
template RandomEvent {
|
||
probability: 0.0..1.0
|
||
damage: 5..20
|
||
}
|
||
</code></pre>
|
||
<h3 id="semantics"><a class="header" href="#semantics">Semantics</a></h3>
|
||
<ul>
|
||
<li><strong>Templates only</strong>: Ranges are only valid in templates</li>
|
||
<li><strong>Instantiation</strong>: When a template is used, a specific value within the range is selected</li>
|
||
<li><strong>Inclusive bounds</strong>: Both <code>min</code> and <code>max</code> are included</li>
|
||
<li><strong>Order matters</strong>: <code>min</code> must be ≤ <code>max</code></li>
|
||
</ul>
|
||
<h3 id="validation-2"><a class="header" href="#validation-2">Validation</a></h3>
|
||
<ul>
|
||
<li>Both bounds same type</li>
|
||
<li><code>min</code> ≤ <code>max</code></li>
|
||
<li>Cannot use ranges in character/species/etc. (only templates)</li>
|
||
</ul>
|
||
<h3 id="use-cases-12"><a class="header" href="#use-cases-12">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Template variation</li>
|
||
<li>Procedural generation</li>
|
||
<li>Random NPC attributes</li>
|
||
<li>Loot table ranges</li>
|
||
</ul>
|
||
<h2 id="identifier"><a class="header" href="#identifier">Identifier</a></h2>
|
||
<p>Reference to another declaration by qualified path.</p>
|
||
<h3 id="syntax-29"><a class="header" href="#syntax-29">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><identifier> ::= <simple-name>
|
||
| <qualified-path>
|
||
|
||
<simple-name> ::= <ident>
|
||
|
||
<qualified-path> ::= <ident> ("::" <ident>)+
|
||
</code></pre>
|
||
<h3 id="examples-13"><a class="header" href="#examples-13">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Martha: Human { // Species reference
|
||
// ...
|
||
}
|
||
|
||
character Elena from Apprentice { // Template reference
|
||
// ...
|
||
}
|
||
|
||
character Guard {
|
||
uses behaviors: [
|
||
{ tree: guards::patrol } // Behavior reference
|
||
]
|
||
uses schedule: DailyRoutine // Schedule reference
|
||
}
|
||
|
||
relationship Partnership {
|
||
Martha // Character reference
|
||
Jane // Character reference
|
||
}
|
||
</code></pre>
|
||
<h3 id="resolution"><a class="header" href="#resolution">Resolution</a></h3>
|
||
<ul>
|
||
<li><strong>Unqualified</strong>: Searches current module, then imports</li>
|
||
<li><strong>Qualified</strong>: <code>module::submodule::Name</code></li>
|
||
<li><strong>Validation</strong>: Compiler ensures all references resolve</li>
|
||
</ul>
|
||
<h3 id="use-cases-13"><a class="header" href="#use-cases-13">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Species typing (<code>: Species</code>)</li>
|
||
<li>Template inheritance (<code>from Template</code>)</li>
|
||
<li>Behavior tree references</li>
|
||
<li>Schedule references</li>
|
||
<li>Relationship participants</li>
|
||
<li>Cross-references</li>
|
||
</ul>
|
||
<h2 id="list"><a class="header" href="#list">List</a></h2>
|
||
<p>Ordered collection of values.</p>
|
||
<h3 id="syntax-30"><a class="header" href="#syntax-30">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><list> ::= "[" "]"
|
||
| "[" <value> ("," <value>)* "]"
|
||
</code></pre>
|
||
<h3 id="examples-14"><a class="header" href="#examples-14">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Mage {
|
||
known_spells: ["Fireball", "Lightning", "Shield"]
|
||
spell_levels: [3, 5, 2]
|
||
party_members: [Martha, Jane, Elena]
|
||
}
|
||
|
||
character Traveler {
|
||
inventory: [
|
||
{item: "Sword", damage: 10},
|
||
{item: "Potion", healing: 50},
|
||
{item: "Key", opens: "TowerDoor"}
|
||
]
|
||
}
|
||
</code></pre>
|
||
<h3 id="type-constraints"><a class="header" href="#type-constraints">Type Constraints</a></h3>
|
||
<ul>
|
||
<li><strong>Homogeneous preferred</strong>: All elements should be the same type</li>
|
||
<li><strong>Heterogeneous allowed</strong>: Mixed types permitted but discouraged</li>
|
||
<li><strong>Empty lists</strong>: <code>[]</code> is valid</li>
|
||
</ul>
|
||
<h3 id="operations"><a class="header" href="#operations">Operations</a></h3>
|
||
<p>Lists are immutable at declaration time. Runtime list operations depend on the execution environment.</p>
|
||
<h3 id="use-cases-14"><a class="header" href="#use-cases-14">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Collections (inventory, spells, party members)</li>
|
||
<li>References (multiple behaviors, schedules, participants)</li>
|
||
<li>Enum-like sets (before proper enums)</li>
|
||
</ul>
|
||
<h2 id="object"><a class="header" href="#object">Object</a></h2>
|
||
<p>Structured data with named fields.</p>
|
||
<h3 id="syntax-31"><a class="header" href="#syntax-31">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><object> ::= "{" "}"
|
||
| "{" <field> ("," <field>)* "}"
|
||
|
||
<field> ::= <identifier> ":" <value>
|
||
</code></pre>
|
||
<h3 id="examples-15"><a class="header" href="#examples-15">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Hero {
|
||
position: {x: 100, y: 200}
|
||
|
||
stats: {
|
||
strength: 15,
|
||
dexterity: 12,
|
||
intelligence: 10
|
||
}
|
||
|
||
equipment: {
|
||
weapon: {
|
||
name: "Longsword",
|
||
damage: 10,
|
||
enchantment: "Fire"
|
||
},
|
||
armor: {
|
||
name: "Plate Mail",
|
||
defense: 8
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="nesting"><a class="header" href="#nesting">Nesting</a></h3>
|
||
<p>Objects can be nested arbitrarily deep:</p>
|
||
<pre><code class="language-storybook">character Complex {
|
||
deep: {
|
||
level1: {
|
||
level2: {
|
||
level3: {
|
||
value: 42
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-cases-15"><a class="header" href="#use-cases-15">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Structured data (position, stats)</li>
|
||
<li>Nested configurations</li>
|
||
<li>Inline data structures</li>
|
||
<li>Complex field values</li>
|
||
</ul>
|
||
<h2 id="prose-blocks-7"><a class="header" href="#prose-blocks-7">Prose Blocks</a></h2>
|
||
<p>Long-form narrative text with semantic tags.</p>
|
||
<h3 id="syntax-32"><a class="header" href="#syntax-32">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><prose-block> ::= "---" <tag> <content> "---"
|
||
|
||
<tag> ::= <identifier>
|
||
|
||
<content> ::= <any-text-except-triple-dash>
|
||
</code></pre>
|
||
<h3 id="common-tags"><a class="header" href="#common-tags">Common Tags</a></h3>
|
||
<ul>
|
||
<li><code>---description</code>: General description</li>
|
||
<li><code>---backstory</code>: Character history</li>
|
||
<li><code>---appearance</code>: Physical description</li>
|
||
<li><code>---personality</code>: Behavioral traits</li>
|
||
<li><code>---motivation</code>: Goals and desires</li>
|
||
<li><code>---notes</code>: Meta-commentary</li>
|
||
<li><code>---ecology</code>: Species habitat/behavior</li>
|
||
<li><code>---culture</code>: Social structures</li>
|
||
<li><code>---narrative</code>: Story context</li>
|
||
</ul>
|
||
<h3 id="examples-16"><a class="header" href="#examples-16">Examples</a></h3>
|
||
<pre><code class="language-storybook">character Martha {
|
||
age: 34
|
||
|
||
---backstory
|
||
Martha learned to bake from her grandmother, starting at age
|
||
twelve. She now runs the most popular bakery in town, known
|
||
for her sourdough bread and unwavering quality standards.
|
||
---
|
||
|
||
---personality
|
||
Meticulous and patient, with an unwavering commitment to
|
||
quality. Tough but fair with her staff, and deeply loyal
|
||
to the customers who have supported her bakery for years.
|
||
---
|
||
}
|
||
|
||
species Dragon {
|
||
lifespan: 1000
|
||
|
||
---ecology
|
||
Dragons nest in high mountain caves and emerge every few decades
|
||
to hunt large prey. They hoard treasure as a mating display.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="formatting"><a class="header" href="#formatting">Formatting</a></h3>
|
||
<ul>
|
||
<li>Leading/trailing whitespace is preserved</li>
|
||
<li>No escape sequences needed</li>
|
||
<li>Can contain any characters except <code>---</code> on its own line</li>
|
||
<li>Indentation is significant for readability but not semantics</li>
|
||
</ul>
|
||
<h3 id="multiple-prose-blocks"><a class="header" href="#multiple-prose-blocks">Multiple Prose Blocks</a></h3>
|
||
<p>A single declaration can have multiple prose blocks with different tags:</p>
|
||
<pre><code class="language-storybook">character Gandalf {
|
||
---appearance
|
||
An old man with a long gray beard, pointed hat, and staff.
|
||
---
|
||
|
||
---backstory
|
||
One of the Istari sent to Middle-earth to contest Sauron.
|
||
---
|
||
|
||
---personality
|
||
Patient and wise, but with a mischievous streak.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-cases-16"><a class="header" href="#use-cases-16">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Character backstories</li>
|
||
<li>Species descriptions</li>
|
||
<li>World-building flavor text</li>
|
||
<li>Design notes</li>
|
||
<li>Narrative context</li>
|
||
</ul>
|
||
<h2 id="override"><a class="header" href="#override">Override</a></h2>
|
||
<p>Template instantiation with field modifications.</p>
|
||
<h3 id="syntax-33"><a class="header" href="#syntax-33">Syntax</a></h3>
|
||
<pre><code class="language-bnf"><override> ::= <qualified-path> "with" "{" <override-op>* "}"
|
||
|
||
<override-op> ::= <identifier> ":" <value> // Set
|
||
| "remove" <identifier> // Remove
|
||
| "append" <identifier> ":" <value> // Append (for lists)
|
||
</code></pre>
|
||
<h3 id="examples-17"><a class="header" href="#examples-17">Examples</a></h3>
|
||
<h4 id="basic-override"><a class="header" href="#basic-override">Basic Override</a></h4>
|
||
<pre><code class="language-storybook">template BaseWarrior {
|
||
strength: 10
|
||
dexterity: 8
|
||
weapon: "Sword"
|
||
}
|
||
|
||
character StrongWarrior {
|
||
stats: BaseWarrior with {
|
||
strength: 15 // Override strength
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h4 id="remove-fields"><a class="header" href="#remove-fields">Remove Fields</a></h4>
|
||
<pre><code class="language-storybook">template FullEquipment {
|
||
helmet: "Iron"
|
||
chest: "Plate"
|
||
legs: "Mail"
|
||
boots: "Leather"
|
||
}
|
||
|
||
character LightFighter {
|
||
equipment: FullEquipment with {
|
||
remove helmet
|
||
remove chest
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h4 id="append-to-lists"><a class="header" href="#append-to-lists">Append to Lists</a></h4>
|
||
<pre><code class="language-storybook">template BasicSpells {
|
||
spells: ["Fireball", "Shield"]
|
||
}
|
||
|
||
character AdvancedMage {
|
||
magic: BasicSpells with {
|
||
append spells: "Teleport"
|
||
append spells: "Lightning"
|
||
}
|
||
// Result: ["Fireball", "Shield", "Teleport", "Lightning"]
|
||
}
|
||
</code></pre>
|
||
<h4 id="complex-override"><a class="header" href="#complex-override">Complex Override</a></h4>
|
||
<pre><code class="language-storybook">template RogueTemplate {
|
||
stealth: 15
|
||
lockpicking: 12
|
||
backstab_damage: 20
|
||
equipment: ["Dagger", "Lockpicks"]
|
||
}
|
||
|
||
character MasterThief {
|
||
abilities: RogueTemplate with {
|
||
stealth: 20 // Set
|
||
remove backstab_damage // Remove
|
||
append equipment: "Grappling Hook" // Append
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="operations-1"><a class="header" href="#operations-1">Operations</a></h3>
|
||
<h4 id="set-default"><a class="header" href="#set-default"><code>set</code> (default)</a></h4>
|
||
<p>Replace a field’s value:</p>
|
||
<pre><code class="language-storybook">field_name: new_value
|
||
</code></pre>
|
||
<h4 id="remove"><a class="header" href="#remove"><code>remove</code></a></h4>
|
||
<p>Delete a field from the template:</p>
|
||
<pre><code class="language-storybook">remove field_name
|
||
</code></pre>
|
||
<h4 id="append"><a class="header" href="#append"><code>append</code></a></h4>
|
||
<p>Add to a list field (field must be a list):</p>
|
||
<pre><code class="language-storybook">append field_name: value_to_add
|
||
</code></pre>
|
||
<h3 id="validation-3"><a class="header" href="#validation-3">Validation</a></h3>
|
||
<ul>
|
||
<li>Base template must exist</li>
|
||
<li>Overridden fields must exist in template</li>
|
||
<li>Removed fields must exist in template</li>
|
||
<li>Appended fields must be lists</li>
|
||
<li>Type compatibility enforced</li>
|
||
</ul>
|
||
<h3 id="use-cases-17"><a class="header" href="#use-cases-17">Use Cases</a></h3>
|
||
<ul>
|
||
<li>Customizing template instances</li>
|
||
<li>Procedural character generation</li>
|
||
<li>Variant creation</li>
|
||
<li>Data composition</li>
|
||
</ul>
|
||
<h2 id="type-coercion"><a class="header" href="#type-coercion">Type Coercion</a></h2>
|
||
<p>Storybook has <strong>no implicit type coercion</strong>. All type conversions must be explicit.</p>
|
||
<p><strong>Not allowed:</strong></p>
|
||
<pre><code class="language-storybook">character Wrong {
|
||
count: "42" // Error: expected int, got string
|
||
flag: 1 // Error: expected bool, got int
|
||
}
|
||
</code></pre>
|
||
<p><strong>Correct:</strong></p>
|
||
<pre><code class="language-storybook">character Right {
|
||
count: 42
|
||
flag: true
|
||
}
|
||
</code></pre>
|
||
<h2 id="null-and-optional"><a class="header" href="#null-and-optional">Null and Optional</a></h2>
|
||
<p>Storybook <strong>does not have <code>null</code></strong>. For optional values, use:</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Template ranges</strong> with 0 as lower bound:</p>
|
||
<pre><code class="language-storybook">template MaybeWeapon {
|
||
weapon_count: 0..5
|
||
}
|
||
</code></pre>
|
||
</li>
|
||
<li>
|
||
<p><strong>Boolean flags</strong>:</p>
|
||
<pre><code class="language-storybook">character Guard {
|
||
has_weapon: false
|
||
}
|
||
</code></pre>
|
||
</li>
|
||
<li>
|
||
<p><strong>Empty lists</strong>:</p>
|
||
<pre><code class="language-storybook">character Unarmed {
|
||
weapons: []
|
||
}
|
||
</code></pre>
|
||
</li>
|
||
</ol>
|
||
<h2 id="summary-table"><a class="header" href="#summary-table">Summary Table</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Mutable?</th><th>Comparable?</th><th>Valid in Templates?</th><th>Notes</th></tr></thead><tbody>
|
||
<tr><td>Int</td><td>No</td><td>Yes</td><td>Yes</td><td>64-bit signed</td></tr>
|
||
<tr><td>Float</td><td>No</td><td>Yes</td><td>Yes</td><td>64-bit IEEE 754</td></tr>
|
||
<tr><td>String</td><td>No</td><td>Yes</td><td>Yes</td><td>UTF-8</td></tr>
|
||
<tr><td>Bool</td><td>No</td><td>Yes</td><td>Yes</td><td>true/false</td></tr>
|
||
<tr><td>Time</td><td>No</td><td>Yes</td><td>No</td><td>HH:MM or HH:MM:SS</td></tr>
|
||
<tr><td>Duration</td><td>No</td><td>Yes</td><td>No</td><td>Compounds (2h30m)</td></tr>
|
||
<tr><td>Range</td><td>No</td><td>No</td><td>Yes (only)</td><td>Template variation</td></tr>
|
||
<tr><td>Identifier</td><td>No</td><td>Yes</td><td>Yes</td><td>Declaration reference</td></tr>
|
||
<tr><td>List</td><td>No</td><td>Yes</td><td>Yes</td><td>Ordered collection</td></tr>
|
||
<tr><td>Object</td><td>No</td><td>Yes</td><td>Yes</td><td>Named fields</td></tr>
|
||
<tr><td>ProseBlock</td><td>No</td><td>No</td><td>Yes</td><td>Narrative text</td></tr>
|
||
<tr><td>Override</td><td>No</td><td>No</td><td>Yes</td><td>Template modification</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="cross-references-10"><a class="header" href="#cross-references-10">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> - Using values in character fields</li>
|
||
<li><a href="reference/./16-other-declarations.html#templates">Templates</a> - Range values and override syntax</li>
|
||
<li><a href="reference/./14-schedules.html">Schedules</a> - Time and duration in schedules</li>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> - Duration in decorators</li>
|
||
<li><a href="reference/./17-expressions.html">Expressions</a> - Using values in conditions</li>
|
||
</ul>
|
||
<h2 id="related-concepts-8"><a class="header" href="#related-concepts-8">Related Concepts</a></h2>
|
||
<ul>
|
||
<li><strong>Immutability</strong>: All values are immutable at declaration time</li>
|
||
<li><strong>Type safety</strong>: Strong static typing with no implicit coercion</li>
|
||
<li><strong>Structural equivalence</strong>: Objects/lists compared by structure, not identity</li>
|
||
<li><strong>Prose as data</strong>: Narrative text is first-class data, not comments</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="validation-rules-15"><a class="header" href="#validation-rules-15">Validation Rules</a></h1>
|
||
<p>The Storybook compiler performs multi-layered validation to catch errors before runtime. This chapter documents all validation rules, organized by declaration type, along with the error messages you can expect and how to fix them.</p>
|
||
<h2 id="validation-layers-1"><a class="header" href="#validation-layers-1">Validation Layers</a></h2>
|
||
<p>Storybook validation happens in four stages:</p>
|
||
<ol>
|
||
<li><strong>Lexical</strong>: Tokenization of raw text (invalid characters, malformed literals)</li>
|
||
<li><strong>Syntactic</strong>: Grammar structure (missing braces, wrong keyword order)</li>
|
||
<li><strong>Semantic</strong>: Cross-reference resolution, type checking, field merging</li>
|
||
<li><strong>Domain</strong>: Narrative-specific constraints (bond ranges, schedule overlaps)</li>
|
||
</ol>
|
||
<p>Errors at earlier stages prevent later stages from running.</p>
|
||
<h2 id="character-validation"><a class="header" href="#character-validation">Character Validation</a></h2>
|
||
<h3 id="required-rules"><a class="header" href="#required-rules">Required Rules</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Unique name</td><td>Character names must be unique within their module</td><td>Error</td></tr>
|
||
<tr><td>Species exists</td><td>If <code>: Species</code> is used, the species must be defined</td><td>Error</td></tr>
|
||
<tr><td>Templates exist</td><td>All templates in <code>from</code> clause must be defined</td><td>Error</td></tr>
|
||
<tr><td>No circular inheritance</td><td>Template chains cannot form cycles</td><td>Error</td></tr>
|
||
<tr><td>Field type consistency</td><td>Field values must match expected types</td><td>Error</td></tr>
|
||
<tr><td>Behavior trees exist</td><td>All <code>uses behaviors</code> references must resolve</td><td>Error</td></tr>
|
||
<tr><td>Schedules exist</td><td>All <code>uses schedule</code> references must resolve</td><td>Error</td></tr>
|
||
<tr><td>Prose tag uniqueness</td><td>Each prose tag can appear at most once per character</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-18"><a class="header" href="#examples-18">Examples</a></h3>
|
||
<p><strong>Species not found:</strong></p>
|
||
<pre><code class="language-storybook">character Martha: Hobbit { // Error: species 'Hobbit' not defined
|
||
age: 34
|
||
}
|
||
</code></pre>
|
||
<p><strong>Fix:</strong> Define the species or correct the reference:</p>
|
||
<pre><code class="language-storybook">species Hobbit {
|
||
lifespan: 130
|
||
}
|
||
|
||
character Martha: Hobbit {
|
||
age: 34
|
||
}
|
||
</code></pre>
|
||
<p><strong>Duplicate character name:</strong></p>
|
||
<pre><code class="language-storybook">character Martha { age: 34 }
|
||
character Martha { age: 36 } // Error: duplicate character name 'Martha'
|
||
</code></pre>
|
||
<h2 id="template-validation"><a class="header" href="#template-validation">Template Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Unique name</td><td>Template names must be unique within their module</td><td>Error</td></tr>
|
||
<tr><td>Includes exist</td><td>All included templates must be defined</td><td>Error</td></tr>
|
||
<tr><td>No circular includes</td><td>Include chains cannot form cycles</td><td>Error</td></tr>
|
||
<tr><td>Range validity</td><td>Range bounds must satisfy min <= max</td><td>Error</td></tr>
|
||
<tr><td>Range type match</td><td>Both bounds of a range must be the same type</td><td>Error</td></tr>
|
||
<tr><td>Strict enforcement</td><td>Characters using strict templates cannot add extra fields</td><td>Error</td></tr>
|
||
<tr><td>Resource links valid</td><td>Behavior/schedule references must resolve</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-19"><a class="header" href="#examples-19">Examples</a></h3>
|
||
<p><strong>Invalid range:</strong></p>
|
||
<pre><code class="language-storybook">template BadRange {
|
||
age: 65..18 // Error: range min (65) must be <= max (18)
|
||
}
|
||
</code></pre>
|
||
<p><strong>Strict template violation:</strong></p>
|
||
<pre><code class="language-storybook">template Rigid strict {
|
||
required_stat: 10
|
||
}
|
||
|
||
character Constrained from Rigid {
|
||
required_stat: 15
|
||
extra_field: 42 // Error: field 'extra_field' not allowed by strict template 'Rigid'
|
||
}
|
||
</code></pre>
|
||
<h2 id="behavior-tree-validation"><a class="header" href="#behavior-tree-validation">Behavior Tree Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>At least one node</td><td>Behavior body must contain at least one node</td><td>Error</td></tr>
|
||
<tr><td>Composite children</td><td><code>choose</code> and <code>then</code> require at least one child</td><td>Error</td></tr>
|
||
<tr><td>Decorator child</td><td>Decorators require exactly one child</td><td>Error</td></tr>
|
||
<tr><td>Subtree exists</td><td><code>include</code> must reference a defined behavior</td><td>Error</td></tr>
|
||
<tr><td>Expression validity</td><td>Condition expressions must be well-formed</td><td>Error</td></tr>
|
||
<tr><td>Duration format</td><td>Decorator durations must be valid (e.g., <code>5s</code>, <code>10m</code>)</td><td>Error</td></tr>
|
||
<tr><td>Repeat count valid</td><td><code>repeat N</code> requires N >= 0</td><td>Error</td></tr>
|
||
<tr><td>Repeat range valid</td><td><code>repeat min..max</code> requires 0 <= min <= max</td><td>Error</td></tr>
|
||
<tr><td>Retry count valid</td><td><code>retry N</code> requires N >= 1</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-20"><a class="header" href="#examples-20">Examples</a></h3>
|
||
<p><strong>Empty composite:</strong></p>
|
||
<pre><code class="language-storybook">behavior Empty {
|
||
choose options {
|
||
// Error: 'choose' requires at least one child
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Invalid subtree reference:</strong></p>
|
||
<pre><code class="language-storybook">behavior Main {
|
||
include NonExistentBehavior // Error: behavior 'NonExistentBehavior' not defined
|
||
}
|
||
</code></pre>
|
||
<h2 id="life-arc-validation"><a class="header" href="#life-arc-validation">Life Arc Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>At least one state</td><td>Life arc must contain at least one state</td><td>Error</td></tr>
|
||
<tr><td>Unique state names</td><td>State names must be unique within the life arc</td><td>Error</td></tr>
|
||
<tr><td>Valid transitions</td><td>Transition targets must reference defined states</td><td>Error</td></tr>
|
||
<tr><td>Expression validity</td><td>Transition conditions must be well-formed</td><td>Error</td></tr>
|
||
<tr><td>Field targets valid</td><td>On-enter field references must resolve</td><td>Error</td></tr>
|
||
<tr><td>Reachable states</td><td>All states should be reachable from initial state</td><td>Warning</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-21"><a class="header" href="#examples-21">Examples</a></h3>
|
||
<p><strong>Invalid transition target:</strong></p>
|
||
<pre><code class="language-storybook">life_arc Broken {
|
||
state active {
|
||
on timer_expired -> nonexistent // Error: state 'nonexistent' not defined
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Unreachable state (warning):</strong></p>
|
||
<pre><code class="language-storybook">life_arc HasOrphan {
|
||
state start {
|
||
on ready -> middle
|
||
}
|
||
|
||
state middle {
|
||
on done -> end
|
||
}
|
||
|
||
state orphan {} // Warning: state 'orphan' is not reachable
|
||
|
||
state end {}
|
||
}
|
||
</code></pre>
|
||
<h2 id="schedule-validation"><a class="header" href="#schedule-validation">Schedule Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Time format</td><td>Times must be valid HH:MM or HH:MM:SS</td><td>Error</td></tr>
|
||
<tr><td>Extends exists</td><td>Base schedule must be defined</td><td>Error</td></tr>
|
||
<tr><td>No circular extends</td><td>Schedule chains cannot form cycles</td><td>Error</td></tr>
|
||
<tr><td>Named blocks unique</td><td>Block names must be unique within a schedule</td><td>Error</td></tr>
|
||
<tr><td>Action references valid</td><td>Action references must resolve to defined behaviors</td><td>Error</td></tr>
|
||
<tr><td>Constraint values valid</td><td>Temporal constraint values must reference defined enums</td><td>Error</td></tr>
|
||
<tr><td>Recurrence names unique</td><td>Recurrence names must be unique within a schedule</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-22"><a class="header" href="#examples-22">Examples</a></h3>
|
||
<p><strong>Invalid time format:</strong></p>
|
||
<pre><code class="language-storybook">schedule Bad {
|
||
block work {
|
||
25:00 - 17:00 // Error: invalid hour '25'
|
||
action: work
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Circular extends:</strong></p>
|
||
<pre><code class="language-storybook">schedule A extends B { }
|
||
schedule B extends A { } // Error: circular schedule extension detected
|
||
</code></pre>
|
||
<h2 id="relationship-validation"><a class="header" href="#relationship-validation">Relationship Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>At least two participants</td><td>Relationships require >= 2 participants</td><td>Error</td></tr>
|
||
<tr><td>Participants exist</td><td>All participant names must reference defined entities</td><td>Error</td></tr>
|
||
<tr><td>Unique participants</td><td>Each participant appears at most once</td><td>Error</td></tr>
|
||
<tr><td>Field type consistency</td><td>Fields must have valid value types</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-23"><a class="header" href="#examples-23">Examples</a></h3>
|
||
<p><strong>Too few participants:</strong></p>
|
||
<pre><code class="language-storybook">relationship Lonely {
|
||
Martha // Error: relationship requires at least 2 participants
|
||
bond: 0.5
|
||
}
|
||
</code></pre>
|
||
<h2 id="species-validation"><a class="header" href="#species-validation">Species Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Unique name</td><td>Species names must be unique within their module</td><td>Error</td></tr>
|
||
<tr><td>No circular includes</td><td>Include chains cannot form cycles</td><td>Error</td></tr>
|
||
<tr><td>Includes exist</td><td>All included species must be defined</td><td>Error</td></tr>
|
||
<tr><td>Field type consistency</td><td>Fields must have valid values</td><td>Error</td></tr>
|
||
<tr><td>Prose tag uniqueness</td><td>Each prose tag can appear at most once</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="enum-validation"><a class="header" href="#enum-validation">Enum Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Unique enum name</td><td>Enum names must be unique within their module</td><td>Error</td></tr>
|
||
<tr><td>Unique variants</td><td>Variant names must be unique within the enum</td><td>Error</td></tr>
|
||
<tr><td>Non-empty</td><td>Enums must have at least one variant</td><td>Error</td></tr>
|
||
<tr><td>Valid identifiers</td><td>Variants must follow identifier rules</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-24"><a class="header" href="#examples-24">Examples</a></h3>
|
||
<p><strong>Duplicate variant:</strong></p>
|
||
<pre><code class="language-storybook">enum Size {
|
||
tiny,
|
||
small,
|
||
small, // Error: duplicate variant 'small' in enum 'Size'
|
||
large
|
||
}
|
||
</code></pre>
|
||
<h2 id="institution-and-location-validation"><a class="header" href="#institution-and-location-validation">Institution and Location Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Unique name</td><td>Names must be unique within their module</td><td>Error</td></tr>
|
||
<tr><td>Resource links valid</td><td>Behavior/schedule references must resolve</td><td>Error</td></tr>
|
||
<tr><td>Field type consistency</td><td>Fields must have valid values</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="expression-validation"><a class="header" href="#expression-validation">Expression Validation</a></h2>
|
||
<p>Expressions are validated wherever they appear (life arc transitions, behavior tree conditions, if decorators).</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Type consistency</td><td>Both sides of comparison must have compatible types</td><td>Error</td></tr>
|
||
<tr><td>Boolean context</td><td>Logical operators require boolean operands</td><td>Error</td></tr>
|
||
<tr><td>Field existence</td><td>Referenced fields must exist on the entity</td><td>Error</td></tr>
|
||
<tr><td>Collection validity</td><td>Quantifiers require collection-typed expressions</td><td>Error</td></tr>
|
||
<tr><td>Variable scope</td><td>Quantifier variables only valid within their predicate</td><td>Error</td></tr>
|
||
<tr><td>Enum validity</td><td>Enum comparisons must reference defined values</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-25"><a class="header" href="#examples-25">Examples</a></h3>
|
||
<p><strong>Type mismatch:</strong></p>
|
||
<pre><code class="language-storybook">life_arc TypeError {
|
||
state checking {
|
||
on count == "five" -> done // Error: cannot compare int with string
|
||
}
|
||
|
||
state done {}
|
||
}
|
||
</code></pre>
|
||
<h2 id="use-statement-validation"><a class="header" href="#use-statement-validation">Use Statement Validation</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>Path exists</td><td>Imported paths must reference defined modules/items</td><td>Error</td></tr>
|
||
<tr><td>No circular imports</td><td>Modules cannot form circular dependency chains</td><td>Error</td></tr>
|
||
<tr><td>Valid identifiers</td><td>All path segments must be valid identifiers</td><td>Error</td></tr>
|
||
<tr><td>Grouped import validity</td><td>All items in <code>{}</code> must exist in the target module</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="examples-26"><a class="header" href="#examples-26">Examples</a></h3>
|
||
<p><strong>Missing import:</strong></p>
|
||
<pre><code class="language-storybook">use schema::nonexistent::Thing; // Error: module 'schema::nonexistent' not found
|
||
</code></pre>
|
||
<h2 id="cross-file-validation"><a class="header" href="#cross-file-validation">Cross-File Validation</a></h2>
|
||
<p>When resolving across multiple <code>.sb</code> files, the compiler performs additional checks:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Rule</th><th>Description</th><th>Severity</th></tr></thead><tbody>
|
||
<tr><td>All references resolve</td><td>Cross-file references must find their targets</td><td>Error</td></tr>
|
||
<tr><td>No naming conflicts</td><td>Declarations must not collide across files in the same module</td><td>Error</td></tr>
|
||
<tr><td>Import visibility</td><td>Only public declarations can be imported</td><td>Error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="common-error-patterns"><a class="header" href="#common-error-patterns">Common Error Patterns</a></h2>
|
||
<h3 id="missing-definitions"><a class="header" href="#missing-definitions">Missing Definitions</a></h3>
|
||
<p>The most common error is referencing something that does not exist:</p>
|
||
<pre><code class="language-storybook">character Martha: Human from Baker {
|
||
specialty: sourdough
|
||
}
|
||
</code></pre>
|
||
<p>If <code>Human</code>, <code>Baker</code>, or the <code>sourdough</code> enum variant are not defined or imported, the compiler will report an error. Fix by adding the appropriate <code>use</code> statements:</p>
|
||
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Specialty};
|
||
use schema::templates::Baker;
|
||
use schema::beings::Human;
|
||
|
||
character Martha: Human from Baker {
|
||
specialty: sourdough
|
||
}
|
||
</code></pre>
|
||
<h3 id="circular-dependencies"><a class="header" href="#circular-dependencies">Circular Dependencies</a></h3>
|
||
<p>Circular references are rejected at every level:</p>
|
||
<ul>
|
||
<li>Templates including each other</li>
|
||
<li>Species including each other</li>
|
||
<li>Schedules extending each other</li>
|
||
<li>Modules importing each other</li>
|
||
</ul>
|
||
<p>Break cycles by restructuring into a hierarchy or extracting shared parts into a common module.</p>
|
||
<h3 id="type-mismatches"><a class="header" href="#type-mismatches">Type Mismatches</a></h3>
|
||
<p>Storybook has no implicit type coercion. Ensure values match their expected types:</p>
|
||
<pre><code class="language-storybook">// Wrong:
|
||
character Bad {
|
||
age: "twenty" // Error: expected int, got string
|
||
is_ready: 1 // Error: expected bool, got int
|
||
}
|
||
|
||
// Correct:
|
||
character Good {
|
||
age: 20
|
||
is_ready: true
|
||
}
|
||
</code></pre>
|
||
<h2 id="validation-summary"><a class="header" href="#validation-summary">Validation Summary</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Declaration</th><th>Key Constraints</th></tr></thead><tbody>
|
||
<tr><td>Character</td><td>Unique name, valid species/templates, no circular inheritance</td></tr>
|
||
<tr><td>Template</td><td>Unique name, valid includes, valid ranges, strict enforcement</td></tr>
|
||
<tr><td>Behavior</td><td>Non-empty, valid composites, valid decorators, valid subtrees</td></tr>
|
||
<tr><td>Life Arc</td><td>Non-empty, unique states, valid transitions, reachable states</td></tr>
|
||
<tr><td>Schedule</td><td>Valid times, valid extends chain, unique block names</td></tr>
|
||
<tr><td>Relationship</td><td>>= 2 participants, valid references</td></tr>
|
||
<tr><td>Species</td><td>Unique name, valid includes, no cycles</td></tr>
|
||
<tr><td>Enum</td><td>Unique name, unique variants, non-empty</td></tr>
|
||
<tr><td>Institution</td><td>Unique name, valid resource links</td></tr>
|
||
<tr><td>Location</td><td>Unique name, valid field types</td></tr>
|
||
<tr><td>Use</td><td>Valid paths, no circular imports</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="cross-references-11"><a class="header" href="#cross-references-11">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="reference/./10-characters.html">Characters</a> - Character-specific validation</li>
|
||
<li><a href="reference/./11-behavior-trees.html">Behavior Trees</a> - Behavior validation</li>
|
||
<li><a href="reference/./13-life-arcs.html">Life Arcs</a> - Life arc validation</li>
|
||
<li><a href="reference/./14-schedules.html">Schedules</a> - Schedule validation</li>
|
||
<li><a href="reference/./17-expressions.html">Expression Language</a> - Expression validation</li>
|
||
<li><a href="reference/./18-value-types.html">Value Types</a> - Type system constraints</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="design-patterns-1"><a class="header" href="#design-patterns-1">Design Patterns</a></h1>
|
||
<p>This chapter presents proven patterns for structuring Storybook projects. These patterns have emerged from building complex narrative simulations and represent best practices for maintainability, reuse, and clarity.</p>
|
||
<h2 id="behavior-tree-patterns"><a class="header" href="#behavior-tree-patterns">Behavior Tree Patterns</a></h2>
|
||
<h3 id="priority-fallback-chain"><a class="header" href="#priority-fallback-chain">Priority Fallback Chain</a></h3>
|
||
<p>Use a selector to try increasingly desperate options:</p>
|
||
<pre><code class="language-storybook">behavior Survival {
|
||
choose survival_priority {
|
||
then optimal {
|
||
if(health > 70 and has_supplies)
|
||
ProceedNormally
|
||
}
|
||
|
||
then cautious {
|
||
if(health > 30)
|
||
ProceedCarefully
|
||
}
|
||
|
||
then desperate {
|
||
if(health > 10)
|
||
SeekHelp
|
||
}
|
||
|
||
LastResortPanic
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>The tree naturally degrades: first tries the best option, then falls back through progressively worse alternatives.</p>
|
||
<h3 id="conditional-behavior-switching"><a class="header" href="#conditional-behavior-switching">Conditional Behavior Switching</a></h3>
|
||
<p>Use guards at the top level to switch between behavioral modes:</p>
|
||
<pre><code class="language-storybook">behavior ModeSwitcher {
|
||
choose mode {
|
||
if(is_combat_mode) {
|
||
include CombatBehavior
|
||
}
|
||
|
||
if(is_exploration_mode) {
|
||
include ExplorationBehavior
|
||
}
|
||
|
||
if(is_social_mode) {
|
||
include SocialBehavior
|
||
}
|
||
|
||
include IdleBehavior
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="composite-subtree-pattern"><a class="header" href="#composite-subtree-pattern">Composite Subtree Pattern</a></h3>
|
||
<p>Break complex behaviors into focused, reusable subtrees:</p>
|
||
<pre><code class="language-storybook">// Atomic subtrees
|
||
behavior Navigate { then nav { PlanPath, FollowPath } }
|
||
behavior Interact { then talk { Approach, Greet, Converse } }
|
||
behavior Trade { then exchange { ShowGoods, Negotiate, Exchange } }
|
||
|
||
// Composed behavior
|
||
behavior Merchant_AI {
|
||
choose activity {
|
||
then serve_customer {
|
||
if(customer_present)
|
||
include Interact
|
||
include Trade
|
||
}
|
||
|
||
then travel_to_market {
|
||
if(is_market_day)
|
||
include Navigate
|
||
}
|
||
|
||
Idle
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="repeating-patrol-with-interrupts"><a class="header" href="#repeating-patrol-with-interrupts">Repeating Patrol with Interrupts</a></h3>
|
||
<p>Use a repeating patrol that can be interrupted by higher-priority events:</p>
|
||
<pre><code class="language-storybook">character Guard {
|
||
uses behaviors: [
|
||
{
|
||
tree: GuardCombat
|
||
when: threat_detected
|
||
priority: high
|
||
},
|
||
{
|
||
tree: GuardPatrol
|
||
priority: normal
|
||
}
|
||
]
|
||
}
|
||
|
||
behavior GuardPatrol {
|
||
repeat {
|
||
then patrol_loop {
|
||
MoveTo(destination: "Waypoint1")
|
||
WaitAndScan(duration: 5s)
|
||
MoveTo(destination: "Waypoint2")
|
||
WaitAndScan(duration: 5s)
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>The combat behavior preempts patrol when threats appear, then patrol resumes.</p>
|
||
<h2 id="character-architecture-patterns"><a class="header" href="#character-architecture-patterns">Character Architecture Patterns</a></h2>
|
||
<h3 id="species--templates-composition"><a class="header" href="#species--templates-composition">Species + Templates Composition</a></h3>
|
||
<p>Use species for identity and templates for capabilities:</p>
|
||
<pre><code class="language-storybook">// Species: What they ARE
|
||
species Human { lifespan: 70 }
|
||
species Elf { lifespan: 1000 }
|
||
|
||
// Templates: What they HAVE
|
||
template Warrior { strength: 10..20, weapon_skill: 0.5..1.0 }
|
||
template Scholar { intelligence: 15..20, books_read: 50..500 }
|
||
template Leader { charisma: 12..18, followers: 5..50 }
|
||
|
||
// Characters: Combine both
|
||
character Aragorn: Human from Warrior, Leader {
|
||
strength: 18
|
||
charisma: 17
|
||
}
|
||
|
||
character Elrond: Elf from Scholar, Leader {
|
||
intelligence: 20
|
||
charisma: 18
|
||
}
|
||
</code></pre>
|
||
<h3 id="strict-templates-for-schema-enforcement"><a class="header" href="#strict-templates-for-schema-enforcement">Strict Templates for Schema Enforcement</a></h3>
|
||
<p>Use strict templates when you need controlled, uniform entities:</p>
|
||
<pre><code class="language-storybook">template RecipeCard strict {
|
||
recipe_name: string
|
||
difficulty: Difficulty
|
||
prep_time_minutes: 10..180
|
||
}
|
||
|
||
// This works:
|
||
character SourdoughRecipe from RecipeCard {
|
||
recipe_name: "Classic Sourdough"
|
||
difficulty: intermediate
|
||
prep_time_minutes: 120
|
||
}
|
||
|
||
// This would error (extra field not allowed):
|
||
// character BadRecipe from RecipeCard {
|
||
// recipe_name: "Mystery Bread"
|
||
// difficulty: easy
|
||
// favorite_color: "blue" // Error!
|
||
// }
|
||
</code></pre>
|
||
<h3 id="template-inheritance-chains"><a class="header" href="#template-inheritance-chains">Template Inheritance Chains</a></h3>
|
||
<p>Build template hierarchies for progressive specialization:</p>
|
||
<pre><code class="language-storybook">template Worker {
|
||
skill_level: 0.0..1.0
|
||
wage: 10..50
|
||
}
|
||
|
||
template SkilledWorker {
|
||
include Worker
|
||
specialization: "general"
|
||
tool_proficiency: 0.5..1.0
|
||
}
|
||
|
||
template MasterCraftsman {
|
||
include SkilledWorker
|
||
can_teach: true
|
||
reputation: 0.7..1.0
|
||
}
|
||
</code></pre>
|
||
<h2 id="relationship-patterns"><a class="header" href="#relationship-patterns">Relationship Patterns</a></h2>
|
||
<h3 id="bidirectional-perspective"><a class="header" href="#bidirectional-perspective">Bidirectional Perspective</a></h3>
|
||
<p>Model relationships where each side sees things differently:</p>
|
||
<pre><code class="language-storybook">relationship MentorApprentice {
|
||
Master as mentor self {
|
||
patience: 0.7
|
||
investment_in_student: 0.9
|
||
} other {
|
||
sees_potential: 0.8
|
||
frustration_level: 0.3
|
||
}
|
||
|
||
Student as apprentice self {
|
||
dedication: 0.8
|
||
overwhelmed: 0.4
|
||
} other {
|
||
respect: 0.95
|
||
desire_to_impress: 0.9
|
||
}
|
||
|
||
bond: 0.75
|
||
years_together: 3
|
||
}
|
||
</code></pre>
|
||
<h3 id="power-dynamic-pattern"><a class="header" href="#power-dynamic-pattern">Power Dynamic Pattern</a></h3>
|
||
<p>Model unequal power relationships explicitly:</p>
|
||
<pre><code class="language-storybook">relationship Vassalage {
|
||
King as lord self {
|
||
authority: 1.0
|
||
grants: "protection"
|
||
} other {
|
||
trusts_vassal: 0.6
|
||
}
|
||
|
||
Knight as vassal self {
|
||
loyalty: 0.9
|
||
ambition: 0.4
|
||
} other {
|
||
respects_lord: 0.8
|
||
fears_lord: 0.3
|
||
}
|
||
|
||
bond: 0.7
|
||
}
|
||
</code></pre>
|
||
<h3 id="relationship-network"><a class="header" href="#relationship-network">Relationship Network</a></h3>
|
||
<p>Build social graphs with multiple overlapping relationships:</p>
|
||
<pre><code class="language-storybook">// Family
|
||
relationship BakerMarriage { Martha as spouse, David as spouse, bond: 0.9 }
|
||
relationship BakerParenting { Martha as parent, Tommy as child, bond: 0.95 }
|
||
|
||
// Professional
|
||
relationship BakerEmployment { Martha as employer, Elena as employee, bond: 0.8 }
|
||
relationship GuildMembership { Martha as member, BakersGuild as org }
|
||
|
||
// Social
|
||
relationship BakerFriendship { Martha, Neighbor, bond: 0.6 }
|
||
</code></pre>
|
||
<h2 id="schedule-patterns"><a class="header" href="#schedule-patterns">Schedule Patterns</a></h2>
|
||
<h3 id="base-schedule-with-specializations"><a class="header" href="#base-schedule-with-specializations">Base Schedule with Specializations</a></h3>
|
||
<pre><code class="language-storybook">schedule BaseWorker {
|
||
block work { 09:00 - 17:00, action: work::standard }
|
||
block lunch { 12:00 - 13:00, action: social::lunch }
|
||
}
|
||
|
||
schedule EarlyBird extends BaseWorker {
|
||
block work { 05:00 - 13:00, action: work::early_shift }
|
||
block lunch { 11:00 - 12:00, action: social::lunch }
|
||
}
|
||
|
||
schedule NightOwl extends BaseWorker {
|
||
block work { 14:00 - 22:00, action: work::late_shift }
|
||
block lunch { 18:00 - 19:00, action: social::dinner }
|
||
}
|
||
</code></pre>
|
||
<h3 id="seasonal-variation-1"><a class="header" href="#seasonal-variation-1">Seasonal Variation</a></h3>
|
||
<pre><code class="language-storybook">schedule FarmSchedule {
|
||
block spring_work {
|
||
06:00 - 18:00
|
||
action: farming::plant
|
||
on season spring
|
||
}
|
||
|
||
block summer_work {
|
||
05:00 - 20:00
|
||
action: farming::tend
|
||
on season summer
|
||
}
|
||
|
||
block fall_work {
|
||
06:00 - 20:00
|
||
action: farming::harvest
|
||
on season fall
|
||
}
|
||
|
||
block winter_work {
|
||
08:00 - 16:00
|
||
action: farming::maintain
|
||
on season winter
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="life-arc-patterns"><a class="header" href="#life-arc-patterns">Life Arc Patterns</a></h2>
|
||
<h3 id="progressive-development"><a class="header" href="#progressive-development">Progressive Development</a></h3>
|
||
<pre><code class="language-storybook">life_arc CareerProgression {
|
||
state novice {
|
||
on enter { Character.title: "Apprentice" }
|
||
on experience > 100 -> intermediate
|
||
}
|
||
|
||
state intermediate {
|
||
on enter { Character.title: "Journeyman" }
|
||
on experience > 500 -> expert
|
||
}
|
||
|
||
state expert {
|
||
on enter { Character.title: "Master", Character.can_teach: true }
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="emotional-state-machine"><a class="header" href="#emotional-state-machine">Emotional State Machine</a></h3>
|
||
<pre><code class="language-storybook">life_arc MoodSystem {
|
||
state neutral {
|
||
on provoked -> angry
|
||
on complimented -> happy
|
||
on tired -> sleepy
|
||
}
|
||
|
||
state angry {
|
||
on enter { Character.aggression: 0.9 }
|
||
on calmed_down -> neutral
|
||
on escalated -> furious
|
||
}
|
||
|
||
state furious {
|
||
on enter { Character.aggression: 1.0 }
|
||
on timeout_elapsed -> angry
|
||
}
|
||
|
||
state happy {
|
||
on enter { Character.gives_discounts: true }
|
||
on insulted -> neutral
|
||
}
|
||
|
||
state sleepy {
|
||
on enter { Character.responsiveness: 0.2 }
|
||
on woke_up -> neutral
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="project-organization-patterns"><a class="header" href="#project-organization-patterns">Project Organization Patterns</a></h2>
|
||
<h3 id="schema--world-separation"><a class="header" href="#schema--world-separation">Schema / World Separation</a></h3>
|
||
<p>Keep type definitions separate from instance data:</p>
|
||
<pre><code>my-project/
|
||
schema/ # Types and templates (reusable)
|
||
core_enums.sb
|
||
templates.sb
|
||
beings.sb
|
||
world/ # Instances (specific to this story)
|
||
characters/
|
||
behaviors/
|
||
relationships/
|
||
locations/
|
||
</code></pre>
|
||
<h3 id="module-per-domain"><a class="header" href="#module-per-domain">Module per Domain</a></h3>
|
||
<p>Group related declarations together:</p>
|
||
<pre><code>world/
|
||
characters/
|
||
heroes.sb # All hero characters
|
||
villains.sb # All villain characters
|
||
npcs.sb # Background characters
|
||
behaviors/
|
||
combat.sb # Combat behaviors
|
||
social.sb # Social behaviors
|
||
exploration.sb # Exploration behaviors
|
||
</code></pre>
|
||
<h2 id="anti-patterns-to-avoid"><a class="header" href="#anti-patterns-to-avoid">Anti-Patterns to Avoid</a></h2>
|
||
<p><strong>Deep nesting</strong>: More than 4-5 levels of behavior tree nesting is hard to read. Use <code>include</code> to flatten.</p>
|
||
<p><strong>God behaviors</strong>: One massive behavior tree doing everything. Break it into focused subtrees.</p>
|
||
<p><strong>Deep species hierarchies</strong>: More than 2-3 levels of species <code>includes</code> is rarely needed. Use templates for variation.</p>
|
||
<p><strong>Duplicated logic</strong>: If two behaviors share logic, extract it into a shared subtree.</p>
|
||
<p><strong>Unnamed nodes</strong>: Always label composite nodes in behavior trees for readability.</p>
|
||
<h2 id="cross-references-12"><a class="header" href="#cross-references-12">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="advanced/../reference/11-behavior-trees.html">Behavior Trees</a> - Complete behavior syntax</li>
|
||
<li><a href="advanced/../reference/10-characters.html">Characters</a> - Character architecture</li>
|
||
<li><a href="advanced/../reference/15-relationships.html">Relationships</a> - Relationship modeling</li>
|
||
<li><a href="advanced/../reference/14-schedules.html">Schedules</a> - Schedule composition</li>
|
||
<li><a href="advanced/../reference/13-life-arcs.html">Life Arcs</a> - State machine patterns</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="the-sbir-binary-format"><a class="header" href="#the-sbir-binary-format">The SBIR Binary Format</a></h1>
|
||
<p>SBIR (Storybook Intermediate Representation) is the compiled binary format produced by the Storybook compiler. It transforms human-readable <code>.sb</code> files into an optimized, machine-consumable format for simulation runtimes.</p>
|
||
<h2 id="compilation-pipeline"><a class="header" href="#compilation-pipeline">Compilation Pipeline</a></h2>
|
||
<pre><code>.sb files → Lexer → Parser → AST → Resolver → SBIR Binary
|
||
</code></pre>
|
||
<ol>
|
||
<li><strong>Lexer</strong>: Tokenizes raw text into tokens</li>
|
||
<li><strong>Parser</strong>: Builds an Abstract Syntax Tree (AST) from tokens</li>
|
||
<li><strong>Resolver</strong>: Validates, resolves cross-references, merges templates, and produces SBIR</li>
|
||
</ol>
|
||
<h2 id="what-sbir-contains"><a class="header" href="#what-sbir-contains">What SBIR Contains</a></h2>
|
||
<p>SBIR represents the fully resolved state of a Storybook project:</p>
|
||
<ul>
|
||
<li><strong>Characters</strong>: All fields resolved (species + templates merged, overrides applied)</li>
|
||
<li><strong>Behaviors</strong>: Behavior trees with all subtree references inlined</li>
|
||
<li><strong>Life Arcs</strong>: State machines with validated transitions</li>
|
||
<li><strong>Schedules</strong>: Time blocks with resolved action references</li>
|
||
<li><strong>Relationships</strong>: Participants with resolved entity references</li>
|
||
<li><strong>Institutions</strong>: Fully resolved field sets</li>
|
||
<li><strong>Locations</strong>: Fully resolved field sets</li>
|
||
<li><strong>Species</strong>: Fully resolved inheritance chains</li>
|
||
<li><strong>Enums</strong>: Complete variant lists</li>
|
||
</ul>
|
||
<h2 id="resolution-process"><a class="header" href="#resolution-process">Resolution Process</a></h2>
|
||
<h3 id="template-merging"><a class="header" href="#template-merging">Template Merging</a></h3>
|
||
<p>When a character uses templates, SBIR contains the fully merged result:</p>
|
||
<p><strong>Source:</strong></p>
|
||
<pre><code class="language-storybook">species Human { lifespan: 70, speed: 1.0 }
|
||
template Warrior { speed: 1.5, strength: 10 }
|
||
|
||
character Conan: Human from Warrior {
|
||
strength: 20
|
||
}
|
||
</code></pre>
|
||
<p><strong>In SBIR, Conan’s fields are:</strong></p>
|
||
<ul>
|
||
<li><code>lifespan: 70</code> (from Human)</li>
|
||
<li><code>speed: 1.5</code> (Warrior overrides Human)</li>
|
||
<li><code>strength: 20</code> (Conan overrides Warrior)</li>
|
||
</ul>
|
||
<h3 id="cross-file-reference-resolution"><a class="header" href="#cross-file-reference-resolution">Cross-File Reference Resolution</a></h3>
|
||
<p>SBIR resolves all <code>use</code> statements and qualified paths. A relationship referencing <code>Martha</code> in a different file is resolved to the concrete character definition.</p>
|
||
<h3 id="validation-4"><a class="header" href="#validation-4">Validation</a></h3>
|
||
<p>Before producing SBIR, the resolver validates all constraints documented in <a href="advanced/../reference/19-validation.html">Validation Rules</a>:</p>
|
||
<ul>
|
||
<li>All references resolve to defined declarations</li>
|
||
<li>No circular dependencies</li>
|
||
<li>Type consistency</li>
|
||
<li>Domain constraints (bond ranges, schedule validity)</li>
|
||
</ul>
|
||
<h2 id="design-goals"><a class="header" href="#design-goals">Design Goals</a></h2>
|
||
<p><strong>Compact</strong>: SBIR strips comments, whitespace, and redundant structure.</p>
|
||
<p><strong>Self-contained</strong>: No external references – everything is resolved and inlined.</p>
|
||
<p><strong>Fast to load</strong>: Simulation runtimes can load SBIR without re-parsing or re-resolving.</p>
|
||
<p><strong>Validated</strong>: If SBIR was produced, the source was valid. Runtimes do not need to re-validate.</p>
|
||
<h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
|
||
<p>SBIR is consumed by simulation runtimes that drive character behavior, schedule execution, life arc transitions, and relationship queries. The specific binary format is implementation-defined and may evolve between versions.</p>
|
||
<p>For the current SBIR specification, see the <a href="advanced/../SBIR-v0.2.0-SPEC.html">SBIR v0.2.0 Spec</a>.</p>
|
||
<h2 id="cross-references-13"><a class="header" href="#cross-references-13">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="advanced/../reference/09-overview.html">Language Overview</a> - Compilation model</li>
|
||
<li><a href="advanced/../reference/19-validation.html">Validation Rules</a> - What is validated before SBIR production</li>
|
||
<li><a href="advanced/./22-integration.html">Integration Guide</a> - How runtimes consume SBIR</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="integration-guide"><a class="header" href="#integration-guide">Integration Guide</a></h1>
|
||
<p>This chapter covers how to integrate Storybook into larger systems – game engines, simulation frameworks, and custom applications.</p>
|
||
<h2 id="architecture-overview"><a class="header" href="#architecture-overview">Architecture Overview</a></h2>
|
||
<p>Storybook operates in two phases:</p>
|
||
<ol>
|
||
<li><strong>Compile time</strong>: <code>.sb</code> files are parsed, validated, and compiled into SBIR</li>
|
||
<li><strong>Runtime</strong>: A simulation engine consumes SBIR and drives character behavior</li>
|
||
</ol>
|
||
<pre><code> Compile Time Runtime
|
||
.sb files → [Storybook Compiler] → SBIR → [Simulation Engine] → Character Actions
|
||
</code></pre>
|
||
<h2 id="the-storybook-compiler"><a class="header" href="#the-storybook-compiler">The Storybook Compiler</a></h2>
|
||
<p>The compiler is a Rust library and CLI tool that processes <code>.sb</code> files.</p>
|
||
<h3 id="cli-usage"><a class="header" href="#cli-usage">CLI Usage</a></h3>
|
||
<pre><code class="language-bash"># Compile a directory of .sb files
|
||
storybook compile path/to/project/
|
||
|
||
# Compile with output path
|
||
storybook compile path/to/project/ -o output.sbir
|
||
|
||
# Validate without producing output
|
||
storybook check path/to/project/
|
||
</code></pre>
|
||
<h3 id="as-a-library"><a class="header" href="#as-a-library">As a Library</a></h3>
|
||
<p>The compiler can be embedded as a Rust dependency:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>use storybook::syntax::parse_file;
|
||
use storybook::resolve::resolve_files;
|
||
|
||
// Parse .sb files into ASTs
|
||
let ast = parse_file(source_code)?;
|
||
|
||
// Resolve across multiple files
|
||
let resolved = resolve_files(vec![ast1, ast2, ast3])?;
|
||
|
||
// Access resolved data
|
||
for character in resolved.characters() {
|
||
println!("{}: {:?}", character.name, character.fields);
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="key-types"><a class="header" href="#key-types">Key Types</a></h3>
|
||
<p>The resolved output provides these primary types:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Description</th></tr></thead><tbody>
|
||
<tr><td><code>ResolvedFile</code></td><td>Container for all resolved declarations</td></tr>
|
||
<tr><td><code>ResolvedCharacter</code></td><td>Character with merged species/template fields</td></tr>
|
||
<tr><td><code>ResolvedBehavior</code></td><td>Behavior tree with resolved subtree references</td></tr>
|
||
<tr><td><code>ResolvedLifeArc</code></td><td>State machine with validated transitions</td></tr>
|
||
<tr><td><code>ResolvedSchedule</code></td><td>Schedule with resolved time blocks</td></tr>
|
||
<tr><td><code>ResolvedRelationship</code></td><td>Relationship with resolved participant references</td></tr>
|
||
<tr><td><code>ResolvedInstitution</code></td><td>Institution with resolved fields</td></tr>
|
||
<tr><td><code>ResolvedLocation</code></td><td>Location with resolved fields</td></tr>
|
||
<tr><td><code>ResolvedSpecies</code></td><td>Species with resolved includes chain</td></tr>
|
||
<tr><td><code>ResolvedEnum</code></td><td>Enum with variant list</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h2 id="runtime-integration"><a class="header" href="#runtime-integration">Runtime Integration</a></h2>
|
||
<h3 id="behavior-tree-execution"><a class="header" href="#behavior-tree-execution">Behavior Tree Execution</a></h3>
|
||
<p>Runtimes are responsible for:</p>
|
||
<ol>
|
||
<li><strong>Tick-based evaluation</strong>: Call the behavior tree root each frame/tick</li>
|
||
<li><strong>Action execution</strong>: Interpret action nodes (e.g., <code>MoveTo</code>, <code>Attack</code>)</li>
|
||
<li><strong>Condition evaluation</strong>: Evaluate expression nodes against current state</li>
|
||
<li><strong>Decorator state</strong>: Maintain timer/counter state for stateful decorators</li>
|
||
</ol>
|
||
<h3 id="life-arc-execution"><a class="header" href="#life-arc-execution">Life Arc Execution</a></h3>
|
||
<ol>
|
||
<li>Track the current state for each life arc instance</li>
|
||
<li>Evaluate transition conditions each tick</li>
|
||
<li>Execute on-enter actions when transitioning</li>
|
||
<li>Maintain state persistence across ticks</li>
|
||
</ol>
|
||
<h3 id="schedule-execution"><a class="header" href="#schedule-execution">Schedule Execution</a></h3>
|
||
<ol>
|
||
<li>Get the current simulation time</li>
|
||
<li>Find the matching schedule block (considering temporal constraints and recurrences)</li>
|
||
<li>Execute the associated behavior tree action</li>
|
||
</ol>
|
||
<h3 id="relationship-queries"><a class="header" href="#relationship-queries">Relationship Queries</a></h3>
|
||
<p>Provide APIs for querying the relationship graph:</p>
|
||
<ul>
|
||
<li>Find all relationships for a character</li>
|
||
<li>Get bond strength between two entities</li>
|
||
<li>Query perspective fields (self/other)</li>
|
||
</ul>
|
||
<h2 id="lsp-integration"><a class="header" href="#lsp-integration">LSP Integration</a></h2>
|
||
<p>Storybook includes a Language Server Protocol (LSP) implementation for editor support:</p>
|
||
<ul>
|
||
<li><strong>Hover information</strong>: Documentation for keywords and declarations</li>
|
||
<li><strong>Go to definition</strong>: Navigate to declaration sources</li>
|
||
<li><strong>Diagnostics</strong>: Real-time error reporting</li>
|
||
<li><strong>Completions</strong>: Context-aware suggestions</li>
|
||
</ul>
|
||
<p>The LSP server reuses the compiler’s parser and resolver, providing the same validation as the CLI.</p>
|
||
<h3 id="editor-setup"><a class="header" href="#editor-setup">Editor Setup</a></h3>
|
||
<p>The Storybook LSP works with any editor that supports LSP:</p>
|
||
<ul>
|
||
<li><strong>VS Code</strong>: Via the Storybook extension</li>
|
||
<li><strong>Zed</strong>: Via the zed-storybook extension</li>
|
||
<li><strong>Other editors</strong>: Any LSP-compatible editor</li>
|
||
</ul>
|
||
<h2 id="tree-sitter-grammar"><a class="header" href="#tree-sitter-grammar">Tree-sitter Grammar</a></h2>
|
||
<p>A Tree-sitter grammar is provided for syntax highlighting and structural queries:</p>
|
||
<pre><code>tree-sitter-storybook/
|
||
grammar.js # Grammar definition
|
||
src/ # Generated parser
|
||
</code></pre>
|
||
<p>The Tree-sitter grammar supports:</p>
|
||
<ul>
|
||
<li>Syntax highlighting in editors</li>
|
||
<li>Structural code queries</li>
|
||
<li>Incremental parsing for large files</li>
|
||
</ul>
|
||
<h2 id="file-organization-for-integration"><a class="header" href="#file-organization-for-integration">File Organization for Integration</a></h2>
|
||
<p>Organize your project to support both authoring and runtime consumption:</p>
|
||
<pre><code>my-game/
|
||
storybook/ # Storybook source files
|
||
schema/
|
||
core_enums.sb
|
||
templates.sb
|
||
beings.sb
|
||
world/
|
||
characters/
|
||
behaviors/
|
||
relationships/
|
||
assets/
|
||
narrative.sbir # Compiled output for runtime
|
||
src/
|
||
narrative_engine.rs # Runtime that consumes SBIR
|
||
</code></pre>
|
||
<h2 id="cross-references-14"><a class="header" href="#cross-references-14">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="advanced/./21-sbir-format.html">SBIR Format</a> - The compiled binary format</li>
|
||
<li><a href="advanced/../reference/19-validation.html">Validation Rules</a> - What the compiler checks</li>
|
||
<li><a href="advanced/../reference/09-overview.html">Language Overview</a> - Compilation pipeline</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="best-practices-7"><a class="header" href="#best-practices-7">Best Practices</a></h1>
|
||
<p>This chapter compiles best practices for writing clear, maintainable, and effective Storybook code. These guidelines apply across all declaration types and project sizes.</p>
|
||
<h2 id="naming-conventions"><a class="header" href="#naming-conventions">Naming Conventions</a></h2>
|
||
<h3 id="declarations"><a class="header" href="#declarations">Declarations</a></h3>
|
||
<p>Use <strong>PascalCase</strong> for all declaration names:</p>
|
||
<pre><code class="language-storybook">character MasterBaker { } // Good
|
||
species DomesticCat { } // Good
|
||
behavior GuardPatrol { } // Good
|
||
|
||
character master_baker { } // Avoid
|
||
behavior guard-patrol { } // Invalid (no hyphens)
|
||
</code></pre>
|
||
<h3 id="fields-3"><a class="header" href="#fields-3">Fields</a></h3>
|
||
<p>Use <strong>snake_case</strong> for field names:</p>
|
||
<pre><code class="language-storybook">character Martha {
|
||
skill_level: 0.95 // Good
|
||
emotional_state: focused // Good
|
||
years_experience: 22 // Good
|
||
|
||
skillLevel: 0.95 // Avoid (camelCase)
|
||
}
|
||
</code></pre>
|
||
<h3 id="behavior-tree-labels"><a class="header" href="#behavior-tree-labels">Behavior Tree Labels</a></h3>
|
||
<p>Use <strong>snake_case</strong> for node labels, with descriptive names:</p>
|
||
<pre><code class="language-storybook">choose survival_instinct { // Good
|
||
then fight_response { } // Good
|
||
then flight_response { } // Good
|
||
}
|
||
|
||
choose s1 { // Avoid (meaningless)
|
||
then a { } // Avoid
|
||
}
|
||
</code></pre>
|
||
<h3 id="enum-variants"><a class="header" href="#enum-variants">Enum Variants</a></h3>
|
||
<p>Use <strong>PascalCase</strong> or <strong>snake_case</strong> consistently within an enum:</p>
|
||
<pre><code class="language-storybook">// PascalCase (good for short names)
|
||
enum Size { Tiny, Small, Normal, Large, Huge }
|
||
|
||
// snake_case (good for compound names)
|
||
enum GovernmentStyle {
|
||
absolute_tyranny,
|
||
constitutional_monarchy,
|
||
direct_democracy
|
||
}
|
||
</code></pre>
|
||
<h2 id="file-organization-1"><a class="header" href="#file-organization-1">File Organization</a></h2>
|
||
<h3 id="separate-schema-from-world"><a class="header" href="#separate-schema-from-world">Separate Schema from World</a></h3>
|
||
<p>Keep reusable type definitions separate from instance data:</p>
|
||
<pre><code>project/
|
||
schema/ # Reusable across stories
|
||
core_enums.sb # Enum definitions
|
||
templates.sb # Template definitions
|
||
beings.sb # Species definitions
|
||
world/ # Specific to this story
|
||
characters/ # Character instances
|
||
behaviors/ # Behavior trees
|
||
relationships/ # Relationship instances
|
||
locations/ # Location instances
|
||
</code></pre>
|
||
<h3 id="one-concern-per-file"><a class="header" href="#one-concern-per-file">One Concern per File</a></h3>
|
||
<p>Group related declarations, but avoid putting unrelated things together:</p>
|
||
<pre><code class="language-storybook">// characters/bakery_staff.sb - Good: related characters together
|
||
character Martha { }
|
||
character Jane { }
|
||
character Elena { }
|
||
|
||
// everything.sb - Avoid: everything in one file
|
||
character Martha { }
|
||
behavior BakeRoutine { }
|
||
schedule DailyRoutine { }
|
||
relationship Partnership { }
|
||
</code></pre>
|
||
<h3 id="explicit-imports"><a class="header" href="#explicit-imports">Explicit Imports</a></h3>
|
||
<p>Prefer explicit imports over wildcards:</p>
|
||
<pre><code class="language-storybook">// Good: clear what is being used
|
||
use schema::core_enums::{SkillLevel, Specialty};
|
||
use schema::beings::Human;
|
||
|
||
// Avoid: unclear dependencies
|
||
use schema::core_enums::*;
|
||
use schema::beings::*;
|
||
</code></pre>
|
||
<h2 id="character-design"><a class="header" href="#character-design">Character Design</a></h2>
|
||
<h3 id="use-species-for-identity-templates-for-traits"><a class="header" href="#use-species-for-identity-templates-for-traits">Use Species for Identity, Templates for Traits</a></h3>
|
||
<pre><code class="language-storybook">// Species: ontological identity
|
||
species Human { lifespan: 70 }
|
||
|
||
// Templates: compositional traits
|
||
template Warrior { strength: 10..20 }
|
||
template Scholar { intelligence: 15..20 }
|
||
|
||
// Character: combines identity and traits
|
||
character Aragorn: Human from Warrior {
|
||
strength: 18
|
||
}
|
||
</code></pre>
|
||
<h3 id="document-with-prose-blocks"><a class="header" href="#document-with-prose-blocks">Document with Prose Blocks</a></h3>
|
||
<pre><code class="language-storybook">character Martha: Human {
|
||
age: 34
|
||
|
||
---backstory
|
||
Martha learned to bake from her grandmother, starting at
|
||
age twelve. She now runs the most popular bakery in town.
|
||
---
|
||
|
||
---personality
|
||
Meticulous and patient, with an unwavering commitment to
|
||
quality. Tough but fair with her staff.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="prefer-flat-inheritance"><a class="header" href="#prefer-flat-inheritance">Prefer Flat Inheritance</a></h3>
|
||
<p>Avoid deep species hierarchies. Two or three levels is usually enough:</p>
|
||
<pre><code class="language-storybook">// Good: shallow
|
||
species Mammal { warm_blooded: true }
|
||
species Human includes Mammal { sapient: true }
|
||
|
||
// Avoid: too deep
|
||
species Being { }
|
||
species LivingBeing includes Being { }
|
||
species Animal includes LivingBeing { }
|
||
species Vertebrate includes Animal { }
|
||
species Mammal includes Vertebrate { }
|
||
species Human includes Mammal { }
|
||
</code></pre>
|
||
<h2 id="behavior-tree-design"><a class="header" href="#behavior-tree-design">Behavior Tree Design</a></h2>
|
||
<h3 id="name-every-composite-node"><a class="header" href="#name-every-composite-node">Name Every Composite Node</a></h3>
|
||
<pre><code class="language-storybook">// Good: self-documenting
|
||
choose daily_priority {
|
||
then handle_emergency { }
|
||
then do_work { }
|
||
then relax { }
|
||
}
|
||
|
||
// Avoid: anonymous nodes
|
||
choose {
|
||
then { }
|
||
then { }
|
||
}
|
||
</code></pre>
|
||
<h3 id="keep-trees-shallow"><a class="header" href="#keep-trees-shallow">Keep Trees Shallow</a></h3>
|
||
<p>Extract deep subtrees into separate behaviors:</p>
|
||
<pre><code class="language-storybook">// Good: flat with includes
|
||
behavior Main {
|
||
choose mode {
|
||
include CombatBehavior
|
||
include ExplorationBehavior
|
||
include SocialBehavior
|
||
}
|
||
}
|
||
|
||
// Avoid: deeply nested
|
||
behavior Main {
|
||
choose {
|
||
then { choose { then { choose { then { Action } } } } }
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-decorators-for-control-flow"><a class="header" href="#use-decorators-for-control-flow">Use Decorators for Control Flow</a></h3>
|
||
<pre><code class="language-storybook">// Good: decorator handles timing
|
||
cooldown(30s) { CastSpell }
|
||
timeout(10s) { SolvePuzzle }
|
||
retry(3) { PickLock }
|
||
|
||
// Avoid: manual timing in actions
|
||
CheckCooldownTimer
|
||
IfCooldownReady { CastSpell }
|
||
</code></pre>
|
||
<h2 id="expression-writing"><a class="header" href="#expression-writing">Expression Writing</a></h2>
|
||
<h3 id="use-parentheses-for-clarity"><a class="header" href="#use-parentheses-for-clarity">Use Parentheses for Clarity</a></h3>
|
||
<pre><code class="language-storybook">// Good: explicit grouping
|
||
on (health < 50 or is_poisoned) and has_antidote -> healing
|
||
|
||
// Risky: relies on precedence knowledge
|
||
on health < 50 or is_poisoned and has_antidote -> healing
|
||
</code></pre>
|
||
<h3 id="break-complex-conditions-into-multiple-transitions"><a class="header" href="#break-complex-conditions-into-multiple-transitions">Break Complex Conditions into Multiple Transitions</a></h3>
|
||
<pre><code class="language-storybook">// Good: separate transitions, easy to read
|
||
state combat {
|
||
on health < 20 and not has_potion -> desperate
|
||
on surrounded and not has_escape -> desperate
|
||
on enemy_count > 10 -> desperate
|
||
}
|
||
|
||
// Avoid: one massive condition
|
||
state combat {
|
||
on (health < 20 and not has_potion) or (surrounded and not has_escape) or enemy_count > 10 -> desperate
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-is-for-enum-comparisons"><a class="header" href="#use-is-for-enum-comparisons">Use <code>is</code> for Enum Comparisons</a></h3>
|
||
<pre><code class="language-storybook">// Good: reads naturally
|
||
on status is active -> active_state
|
||
on skill_level is master -> teach_others
|
||
|
||
// Works but less readable
|
||
on status == active -> active_state
|
||
</code></pre>
|
||
<h2 id="schedule-design"><a class="header" href="#schedule-design">Schedule Design</a></h2>
|
||
<h3 id="use-named-blocks-for-override-support"><a class="header" href="#use-named-blocks-for-override-support">Use Named Blocks for Override Support</a></h3>
|
||
<pre><code class="language-storybook">// Good: named blocks can be overridden
|
||
schedule Base {
|
||
block work { 09:00 - 17:00, action: standard_work }
|
||
}
|
||
|
||
schedule Variant extends Base {
|
||
block work { 05:00 - 13:00, action: early_work }
|
||
}
|
||
</code></pre>
|
||
<h3 id="group-related-blocks"><a class="header" href="#group-related-blocks">Group Related Blocks</a></h3>
|
||
<pre><code class="language-storybook">schedule DailyRoutine {
|
||
// Morning
|
||
block wake { 06:00 - 07:00, action: morning_routine }
|
||
block breakfast { 07:00 - 08:00, action: eat }
|
||
|
||
// Work
|
||
block commute { 08:00 - 09:00, action: travel }
|
||
block work { 09:00 - 17:00, action: work }
|
||
|
||
// Evening
|
||
block leisure { 18:00 - 22:00, action: relax }
|
||
block sleep { 22:00 - 06:00, action: sleep }
|
||
}
|
||
</code></pre>
|
||
<h2 id="relationship-design"><a class="header" href="#relationship-design">Relationship Design</a></h2>
|
||
<h3 id="use-roles-for-clarity"><a class="header" href="#use-roles-for-clarity">Use Roles for Clarity</a></h3>
|
||
<pre><code class="language-storybook">// Good: roles clarify the relationship
|
||
relationship Marriage {
|
||
Martha as spouse
|
||
David as spouse
|
||
bond: 0.9
|
||
}
|
||
|
||
// Less clear without roles
|
||
relationship Marriage {
|
||
Martha
|
||
David
|
||
bond: 0.9
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-perspectives-for-asymmetry"><a class="header" href="#use-perspectives-for-asymmetry">Use Perspectives for Asymmetry</a></h3>
|
||
<pre><code class="language-storybook">// Good: captures different viewpoints
|
||
relationship TeacherStudent {
|
||
Gandalf as teacher self { patience: 0.8 } other { potential: 0.9 }
|
||
Frodo as student self { motivation: 0.7 } other { admiration: 0.95 }
|
||
bond: 0.85
|
||
}
|
||
</code></pre>
|
||
<h2 id="general-principles"><a class="header" href="#general-principles">General Principles</a></h2>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Readability over brevity</strong>: Storybook code should read like a narrative, not a puzzle.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Explicit over implicit</strong>: Say what you mean. Use named nodes, explicit imports, and clear field names.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Flat over deep</strong>: Shallow hierarchies, short behavior trees, and focused files are easier to maintain.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Composition over inheritance</strong>: Prefer combining templates over building deep species hierarchies.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Document with prose</strong>: Prose blocks are a feature, not clutter. Use them to explain intent alongside data.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>One concept per declaration</strong>: Each behavior tree, life arc, or schedule should have a single clear purpose.</p>
|
||
</li>
|
||
</ol>
|
||
<h2 id="cross-references-15"><a class="header" href="#cross-references-15">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="advanced/./20-patterns.html">Design Patterns</a> - Common structural patterns</li>
|
||
<li><a href="advanced/../reference/19-validation.html">Validation Rules</a> - What the compiler checks</li>
|
||
<li><a href="advanced/../reference/09-overview.html">Language Overview</a> - Language philosophy</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="baker-family-complete"><a class="header" href="#baker-family-complete">Baker Family Complete</a></h1>
|
||
<p>This example demonstrates a complete Storybook project modeling Martha’s bakery and the people around it. It showcases all major language features working together.</p>
|
||
<h2 id="project-structure-2"><a class="header" href="#project-structure-2">Project Structure</a></h2>
|
||
<pre><code>baker-family/
|
||
schema/
|
||
core_enums.sb # Enum definitions
|
||
templates.sb # Reusable templates
|
||
beings.sb # Species definitions
|
||
world/
|
||
characters/
|
||
martha.sb # Martha, master baker
|
||
jane.sb # Jane, pastry specialist
|
||
elena.sb # Elena, apprentice
|
||
gregory.sb # Gregory, regular customer
|
||
family.sb # David, Tommy, Emma
|
||
behaviors/
|
||
bakery_behaviors.sb
|
||
relationships/
|
||
bakery_relationships.sb
|
||
locations/
|
||
bakery_places.sb
|
||
institutions/
|
||
bakery_institutions.sb
|
||
schedules/
|
||
bakery_schedules.sb
|
||
</code></pre>
|
||
<h2 id="schema-layer"><a class="header" href="#schema-layer">Schema Layer</a></h2>
|
||
<h3 id="enums-1"><a class="header" href="#enums-1">Enums</a></h3>
|
||
<pre><code class="language-storybook">// schema/core_enums.sb
|
||
|
||
enum SkillLevel {
|
||
novice,
|
||
beginner,
|
||
intermediate,
|
||
advanced,
|
||
expert,
|
||
master
|
||
}
|
||
|
||
enum Specialty {
|
||
sourdough,
|
||
pastries,
|
||
cakes,
|
||
general,
|
||
bread
|
||
}
|
||
|
||
enum Confidence {
|
||
timid,
|
||
uncertain,
|
||
growing,
|
||
steady,
|
||
confident,
|
||
commanding
|
||
}
|
||
|
||
enum DayPart {
|
||
early_morning,
|
||
morning,
|
||
midday,
|
||
afternoon,
|
||
evening,
|
||
night
|
||
}
|
||
</code></pre>
|
||
<h3 id="species-1"><a class="header" href="#species-1">Species</a></h3>
|
||
<pre><code class="language-storybook">// schema/beings.sb
|
||
|
||
species Human {
|
||
lifespan: 80
|
||
|
||
---description
|
||
Bipedal mammals with complex language and tool use.
|
||
---
|
||
}
|
||
|
||
species Cat {
|
||
lifespan: 15
|
||
|
||
---description
|
||
Domestic cats often found in bakeries for pest control
|
||
and companionship.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="templates-1"><a class="header" href="#templates-1">Templates</a></h3>
|
||
<pre><code class="language-storybook">// schema/templates.sb
|
||
|
||
template SkilledWorker {
|
||
skill_level: SkillLevel
|
||
confidence: Confidence
|
||
years_experience: 0..50
|
||
can_work_independently: false
|
||
}
|
||
|
||
template Baker {
|
||
include SkilledWorker
|
||
specialty: Specialty
|
||
recipes_mastered: 0..200
|
||
sourdough_starter_health: 0.0..1.0
|
||
}
|
||
|
||
template BusinessOwner {
|
||
include SkilledWorker
|
||
revenue_monthly: 0..100000
|
||
employees: 0..20
|
||
years_in_business: 0..50
|
||
}
|
||
|
||
template Apprentice {
|
||
include SkilledWorker
|
||
skill_level: novice
|
||
confidence: timid
|
||
mentor: string
|
||
dedication: 0.0..1.0
|
||
}
|
||
</code></pre>
|
||
<h2 id="characters-1"><a class="header" href="#characters-1">Characters</a></h2>
|
||
<h3 id="martha"><a class="header" href="#martha">Martha</a></h3>
|
||
<pre><code class="language-storybook">// world/characters/martha.sb
|
||
|
||
use schema::core_enums::{SkillLevel, Specialty, Confidence};
|
||
use schema::templates::{Baker, BusinessOwner};
|
||
use schema::beings::Human;
|
||
|
||
character Martha: Human from Baker, BusinessOwner {
|
||
uses behaviors: [
|
||
{ tree: BakerMorningRoutine },
|
||
{ tree: HandleEmergency, when: emergency_detected, priority: critical }
|
||
]
|
||
|
||
age: 34
|
||
specialty: sourdough
|
||
skill_level: master
|
||
confidence: commanding
|
||
recipes_mastered: 85
|
||
years_experience: 22
|
||
sourdough_starter_health: 0.95
|
||
|
||
revenue_monthly: 12000
|
||
employees: 3
|
||
years_in_business: 8
|
||
|
||
---backstory
|
||
Martha learned to bake from her grandmother, starting at age twelve.
|
||
By twenty she had won regional competitions. At twenty-six she opened
|
||
her own bakery, which quickly became the most popular in town. Her
|
||
sourdough is legendary -- she maintains a starter that is fifteen
|
||
years old.
|
||
---
|
||
}
|
||
|
||
life_arc MarthaCareerArc {
|
||
---description
|
||
Tracks Martha's evolution from established baker to community leader.
|
||
---
|
||
|
||
state running_bakery {
|
||
on enter {
|
||
Martha.confidence: commanding
|
||
Martha.skill_level: master
|
||
}
|
||
on employees > 5 -> expanding
|
||
}
|
||
|
||
state expanding {
|
||
on enter {
|
||
Martha.revenue_monthly: 20000
|
||
}
|
||
on second_location_opened -> community_leader
|
||
}
|
||
|
||
state community_leader {
|
||
on enter {
|
||
Martha.can_teach: true
|
||
Martha.mentors_count: 3
|
||
}
|
||
|
||
---narrative
|
||
Martha's bakery has become a training ground for the next
|
||
generation of bakers. She sits on the guild board and her
|
||
sourdough recipe is studied at culinary schools.
|
||
---
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="jane"><a class="header" href="#jane">Jane</a></h3>
|
||
<pre><code class="language-storybook">// world/characters/jane.sb
|
||
|
||
use schema::core_enums::{SkillLevel, Specialty, Confidence};
|
||
use schema::templates::Baker;
|
||
use schema::beings::Human;
|
||
|
||
character Jane: Human from Baker {
|
||
age: 36
|
||
specialty: pastries
|
||
skill_level: expert
|
||
confidence: confident
|
||
recipes_mastered: 120
|
||
years_experience: 18
|
||
can_work_independently: true
|
||
|
||
---backstory
|
||
Jane trained at a prestigious culinary school before joining
|
||
Martha's bakery as co-owner. Where Martha excels at bread, Jane
|
||
is a pastry artist. Her croissants draw customers from three
|
||
towns over.
|
||
---
|
||
|
||
---appearance
|
||
A focused woman with flour-dusted apron and steady hands.
|
||
Known for her intricate pastry decorations and precise
|
||
temperature control.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="elena"><a class="header" href="#elena">Elena</a></h3>
|
||
<pre><code class="language-storybook">// world/characters/elena.sb
|
||
|
||
use schema::core_enums::{SkillLevel, Confidence};
|
||
use schema::templates::Apprentice;
|
||
use schema::beings::Human;
|
||
|
||
character Elena: Human from Apprentice {
|
||
age: 16
|
||
skill_level: novice
|
||
confidence: timid
|
||
mentor: "Martha"
|
||
dedication: 0.9
|
||
natural_talent: 0.8
|
||
recipes_mastered: 2
|
||
|
||
---backstory
|
||
Elena comes from a family of farmers who could never afford to
|
||
buy bread from the bakery. When Martha offered her an apprenticeship,
|
||
she jumped at the chance to learn a trade.
|
||
---
|
||
}
|
||
|
||
life_arc ElenaCareer {
|
||
---description
|
||
Tracks Elena's progression from nervous apprentice to confident
|
||
master baker. Each state represents a key phase of her career.
|
||
---
|
||
|
||
state early_apprentice {
|
||
on enter {
|
||
Elena.skill_level: novice
|
||
Elena.confidence: timid
|
||
}
|
||
|
||
on recipes_mastered > 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 > 30 -> senior_journeyman
|
||
|
||
---narrative
|
||
Elena runs the morning shift alone while Martha handles
|
||
special orders. Customers start asking for "Elena's rolls."
|
||
She begins experimenting with her own recipes.
|
||
---
|
||
}
|
||
|
||
state senior_journeyman {
|
||
on enter {
|
||
Elena.skill_level: advanced
|
||
Elena.confidence: steady
|
||
}
|
||
|
||
on recipes_mastered > 50 -> master
|
||
|
||
---narrative
|
||
Elena develops her signature recipe: rosemary olive bread
|
||
that becomes the bakery's bestseller. She handles difficult
|
||
customers with grace and trains new helpers.
|
||
---
|
||
}
|
||
|
||
state master {
|
||
on enter {
|
||
Elena.skill_level: master
|
||
Elena.confidence: commanding
|
||
Elena.can_teach: true
|
||
}
|
||
|
||
---narrative
|
||
Master Baker Elena. She has earned it. The guild acknowledges
|
||
her mastery, and Martha beams with pride. Elena begins
|
||
mentoring her own apprentice.
|
||
---
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="gregory"><a class="header" href="#gregory">Gregory</a></h3>
|
||
<pre><code class="language-storybook">// world/characters/gregory.sb
|
||
|
||
use schema::beings::Human;
|
||
|
||
character Gregory: Human {
|
||
age: 68
|
||
occupation: "retired_teacher"
|
||
always_orders: "sourdough_loaf"
|
||
visits_daily: true
|
||
years_as_customer: 15
|
||
knows_everyone: true
|
||
|
||
---backstory
|
||
Gregory has been buying Martha's bread every morning for
|
||
fifteen years. Their brief daily exchanges about the weather
|
||
and local gossip are a comforting routine for both of them.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="behaviors"><a class="header" href="#behaviors">Behaviors</a></h2>
|
||
<pre><code class="language-storybook">// world/behaviors/bakery_behaviors.sb
|
||
|
||
behavior BakerMorningRoutine {
|
||
---description
|
||
Martha's morning routine: prepare dough step by step,
|
||
from mixing to shaping to baking.
|
||
---
|
||
|
||
then morning_baking {
|
||
// Start with sourdough
|
||
then prepare_starter {
|
||
CheckStarter
|
||
FeedStarter
|
||
WaitForActivity
|
||
}
|
||
|
||
// Mix the dough
|
||
then mix_dough {
|
||
MeasureFlour
|
||
AddWater
|
||
IncorporateStarter
|
||
}
|
||
|
||
// Knead and shape
|
||
then shape_loaves {
|
||
KneadDough
|
||
FirstRise
|
||
ShapeLoaves
|
||
}
|
||
|
||
// Bake
|
||
then bake {
|
||
PreHeatOven
|
||
LoadLoaves
|
||
MonitorBaking
|
||
}
|
||
}
|
||
}
|
||
|
||
behavior CustomerServiceLoop {
|
||
---description
|
||
The bakery's continuous customer service loop. Uses infinite
|
||
repeat decorator to serve customers throughout the day.
|
||
---
|
||
|
||
repeat {
|
||
then service_cycle {
|
||
// Check for customers
|
||
choose service_mode {
|
||
then serve_waiting {
|
||
if(customer_waiting)
|
||
GreetCustomer
|
||
TakeOrder
|
||
}
|
||
|
||
then restock_display {
|
||
if(display_low)
|
||
FetchFromKitchen
|
||
ArrangeOnShelves
|
||
}
|
||
}
|
||
|
||
// Process payment
|
||
CollectPayment
|
||
ThankCustomer
|
||
|
||
// Brief pause between customers
|
||
timeout(5s) {
|
||
CleanCounter
|
||
}
|
||
|
||
PrepareForNextCustomer
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="relationships-1"><a class="header" href="#relationships-1">Relationships</a></h2>
|
||
<pre><code class="language-storybook">// world/relationships/bakery_relationships.sb
|
||
|
||
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.
|
||
---
|
||
}
|
||
}
|
||
|
||
relationship MentorApprentice {
|
||
Martha {
|
||
role: mentor
|
||
teaching_style: "patient"
|
||
investment: 0.9
|
||
|
||
---perspective
|
||
Martha sees Elena as the daughter she might have had in
|
||
the trade. She recognizes the same passion she felt at
|
||
that age and pushes Elena harder because she knows the
|
||
talent is there. Every correction comes from love.
|
||
---
|
||
}
|
||
|
||
Elena {
|
||
role: apprentice
|
||
dedication: 0.9
|
||
anxiety: 0.4
|
||
|
||
---perspective
|
||
Elena idolizes Martha's skill but fears disappointing
|
||
her. Every morning she arrives thirty minutes early to
|
||
practice techniques before Martha gets in. She keeps a
|
||
notebook of every correction, reviewing them each night.
|
||
---
|
||
}
|
||
|
||
bond: 0.85
|
||
}
|
||
|
||
relationship BakeryPartnership {
|
||
Martha {
|
||
role: co_owner
|
||
specialty: "bread"
|
||
handles_finances: true
|
||
|
||
---perspective
|
||
Martha and Jane complement each other perfectly. Martha
|
||
handles the bread and business side while Jane creates
|
||
the pastries that draw customers in. Together they have
|
||
built something neither could alone.
|
||
---
|
||
}
|
||
|
||
Jane {
|
||
role: co_owner
|
||
specialty: "pastries"
|
||
handles_creativity: true
|
||
|
||
---perspective
|
||
Jane considers Martha the steady foundation of their
|
||
partnership. While Jane experiments and creates, Martha
|
||
ensures the bakery runs like clockwork. Their different
|
||
strengths make the bakery stronger.
|
||
---
|
||
}
|
||
|
||
bond: 0.9
|
||
}
|
||
</code></pre>
|
||
<h2 id="locations-2"><a class="header" href="#locations-2">Locations</a></h2>
|
||
<pre><code class="language-storybook">// world/locations/bakery_places.sb
|
||
|
||
location MarthasBakery {
|
||
type: "commercial"
|
||
established: "2018"
|
||
square_feet: 1200
|
||
has_kitchen: true
|
||
has_storefront: true
|
||
seating_capacity: 12
|
||
|
||
---description
|
||
A warm, inviting bakery on Main Street. The aroma of fresh
|
||
bread wafts out the door every morning at 4 AM. Exposed brick
|
||
walls, a glass display case, and a view into the kitchen where
|
||
customers can watch the bakers at work.
|
||
---
|
||
}
|
||
|
||
location FarmersMarket {
|
||
type: "outdoor_market"
|
||
operates_on: "saturday"
|
||
stalls: 30
|
||
foot_traffic: "high"
|
||
|
||
---description
|
||
The weekly Saturday market where Martha sells her bread directly
|
||
to the community. Her stall is always the first to sell out.
|
||
---
|
||
}
|
||
|
||
location BakeryKitchen {
|
||
type: "commercial_kitchen"
|
||
ovens: 3
|
||
prep_stations: 4
|
||
walk_in_cooler: true
|
||
|
||
---description
|
||
The heart of the bakery. Three professional ovens line the back
|
||
wall, each at a different temperature for different breads. The
|
||
sourdough starter sits on a shelf near the warmest oven, bubbling
|
||
contentedly in its ceramic crock.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="institutions-2"><a class="header" href="#institutions-2">Institutions</a></h2>
|
||
<pre><code class="language-storybook">// world/institutions/bakery_institutions.sb
|
||
|
||
institution BakersGuild {
|
||
type: professional_guild
|
||
members: 45
|
||
founded: "1952"
|
||
meets_monthly: true
|
||
|
||
---description
|
||
The local bakers' guild that sets quality standards, organizes
|
||
competitions, and mentors apprentices. Martha has been a board
|
||
member for three years.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="key-takeaways-1"><a class="header" href="#key-takeaways-1">Key Takeaways</a></h2>
|
||
<p>This example demonstrates:</p>
|
||
<ol>
|
||
<li><strong>Layered architecture</strong>: Schema (types) separated from world (instances)</li>
|
||
<li><strong>Species + Templates</strong>: <code>Human</code> species combined with <code>Baker</code> and <code>BusinessOwner</code> templates</li>
|
||
<li><strong>Rich behavior trees</strong>: Morning routine and customer service with choose, then, and repeat</li>
|
||
<li><strong>Asymmetric relationships</strong>: Martha and Elena see their mentorship differently</li>
|
||
<li><strong>Life arcs</strong>: Elena’s career journey modeled as a state machine</li>
|
||
<li><strong>Prose everywhere</strong>: Every declaration includes narrative context</li>
|
||
</ol>
|
||
<h2 id="cross-references-16"><a class="header" href="#cross-references-16">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="examples/../tutorial/01-welcome.html">Tutorial</a> - Step-by-step learning</li>
|
||
<li><a href="examples/../reference/10-characters.html">Characters Reference</a> - Character syntax</li>
|
||
<li><a href="examples/../reference/11-behavior-trees.html">Behavior Trees Reference</a> - Behavior syntax</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="day-in-the-life"><a class="header" href="#day-in-the-life">Day in the Life</a></h1>
|
||
<p>This example models a complete day for a baker character, showing how schedules, behaviors, and life arcs work together to create a rich daily simulation.</p>
|
||
<h2 id="the-baker"><a class="header" href="#the-baker">The Baker</a></h2>
|
||
<pre><code class="language-storybook">use schema::core_enums::{Season, DayOfWeek};
|
||
use schema::beings::Human;
|
||
use schema::templates::SkilledWorker;
|
||
|
||
character Martha: Human from SkilledWorker {
|
||
age: 42
|
||
occupation: "Master Baker"
|
||
skill_level: 0.95
|
||
energy: 1.0
|
||
mood: 0.8
|
||
|
||
uses schedule: MarthaDailySchedule
|
||
uses behaviors: [
|
||
{ tree: BakerRoutine, priority: normal },
|
||
{ tree: HandleEmergency, when: emergency_detected, priority: critical }
|
||
]
|
||
|
||
---backstory
|
||
Martha has been baking since she was twelve, learning from her
|
||
grandmother. She now runs the most popular bakery in town and
|
||
is known for her sourdough bread and apple pastries.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="the-schedule"><a class="header" href="#the-schedule">The Schedule</a></h2>
|
||
<pre><code class="language-storybook">schedule MarthaDailySchedule {
|
||
block wake_up {
|
||
04:00 - 04:30
|
||
action: routines::morning_wake
|
||
}
|
||
|
||
block early_baking {
|
||
04:30 - 07:00
|
||
action: baking::prepare_morning_goods
|
||
}
|
||
|
||
block open_shop {
|
||
07:00 - 07:15
|
||
action: shop::open_for_business
|
||
}
|
||
|
||
block morning_rush {
|
||
07:15 - 10:00
|
||
action: shop::serve_morning_customers
|
||
}
|
||
|
||
block midday_baking {
|
||
10:00 - 12:00
|
||
action: baking::prepare_afternoon_goods
|
||
}
|
||
|
||
block lunch_break {
|
||
12:00 - 13:00
|
||
action: social::lunch_with_family
|
||
}
|
||
|
||
block afternoon_sales {
|
||
13:00 - 16:00
|
||
action: shop::serve_afternoon_customers
|
||
}
|
||
|
||
block close_shop {
|
||
16:00 - 16:30
|
||
action: shop::close_for_day
|
||
}
|
||
|
||
block evening_prep {
|
||
16:30 - 17:30
|
||
action: baking::prepare_dough_for_tomorrow
|
||
}
|
||
|
||
block family_time {
|
||
18:00 - 21:00
|
||
action: social::family_evening
|
||
}
|
||
|
||
block sleep {
|
||
21:00 - 04:00
|
||
action: routines::sleep
|
||
}
|
||
|
||
// Saturday: Market day
|
||
recurs MarketDay on day saturday {
|
||
block market_prep {
|
||
03:00 - 05:00
|
||
action: baking::market_batch
|
||
}
|
||
|
||
block market_sales {
|
||
06:00 - 14:00
|
||
action: market::sell_at_stall
|
||
}
|
||
|
||
block market_cleanup {
|
||
14:00 - 15:00
|
||
action: market::pack_up
|
||
}
|
||
}
|
||
|
||
// Summer: Extended hours
|
||
block summer_afternoon {
|
||
13:00 - 18:00
|
||
action: shop::extended_summer_hours
|
||
on season summer
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="behaviors-1"><a class="header" href="#behaviors-1">Behaviors</a></h2>
|
||
<h3 id="morning-routine"><a class="header" href="#morning-routine">Morning Routine</a></h3>
|
||
<pre><code class="language-storybook">behavior BakerMorningRoutine {
|
||
then morning_sequence {
|
||
WakeUp
|
||
WashFace
|
||
DressInWorkClothes
|
||
|
||
// Check the sourdough starter
|
||
then check_starter {
|
||
ExamineStarter
|
||
if(starter_healthy) {
|
||
FeedStarter
|
||
}
|
||
}
|
||
|
||
WalkToKitchen
|
||
LightOven
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="baking-behavior"><a class="header" href="#baking-behavior">Baking Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior BakerRoutine {
|
||
choose baking_priority {
|
||
// Handle special orders first
|
||
then special_orders {
|
||
if(has_special_orders)
|
||
then fill_order {
|
||
ReviewOrderDetails
|
||
GatherSpecialIngredients
|
||
PrepareSpecialItem
|
||
PackageForCustomer
|
||
}
|
||
}
|
||
|
||
// Regular daily baking
|
||
then daily_bread {
|
||
then sourdough {
|
||
MixDough(recipe: "sourdough", quantity: 10)
|
||
KneadDough(duration: 15m)
|
||
FirstRise(duration: 2h)
|
||
ShapLoaves
|
||
SecondRise(duration: 1h)
|
||
BakeLoaves(temperature: 230, duration: 35m)
|
||
}
|
||
}
|
||
|
||
// Pastries if time permits
|
||
then pastries {
|
||
succeed_always {
|
||
then apple_pastries {
|
||
PrepareFillingApple
|
||
RollPastryDough
|
||
AssemblePastries
|
||
BakePastries(temperature: 200, duration: 25m)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="customer-service"><a class="header" href="#customer-service">Customer Service</a></h3>
|
||
<pre><code class="language-storybook">behavior ServeCustomer {
|
||
then service_sequence {
|
||
GreetCustomer
|
||
if(customer_is_regular) {
|
||
RecallPreferences
|
||
}
|
||
|
||
choose service_type {
|
||
then take_order {
|
||
if(customer_knows_what_they_want)
|
||
AcceptOrder
|
||
PackageItem
|
||
}
|
||
|
||
then help_decide {
|
||
if(not customer_knows_what_they_want)
|
||
OfferRecommendation
|
||
ProvidesSample
|
||
AcceptOrder
|
||
PackageItem
|
||
}
|
||
}
|
||
|
||
CollectPayment
|
||
ThankCustomer
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="emergency-handling"><a class="header" href="#emergency-handling">Emergency Handling</a></h3>
|
||
<pre><code class="language-storybook">behavior HandleEmergency {
|
||
choose emergency_type {
|
||
then oven_fire {
|
||
if(oven_overheating)
|
||
TurnOffOven
|
||
GrabFireExtinguisher
|
||
ExtinguishFire
|
||
AssessDamage
|
||
}
|
||
|
||
then ingredient_shortage {
|
||
if(critical_ingredient_missing)
|
||
CheckBackupSupply
|
||
choose procurement {
|
||
SendApprenticeToMarket
|
||
SubstituteIngredient
|
||
AdjustMenu
|
||
}
|
||
}
|
||
|
||
then equipment_failure {
|
||
if(equipment_broken)
|
||
StopProduction
|
||
AttemptQuickFix
|
||
choose fallback {
|
||
UseBackupEquipment
|
||
CallRepairPerson
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="life-arc-career-and-energy"><a class="header" href="#life-arc-career-and-energy">Life Arc: Career and Energy</a></h2>
|
||
<pre><code class="language-storybook">life_arc MarthaEnergyLevel {
|
||
state rested {
|
||
on enter {
|
||
Martha.energy: 1.0
|
||
Martha.mood: 0.8
|
||
}
|
||
on energy < 0.5 -> tired
|
||
}
|
||
|
||
state tired {
|
||
on enter {
|
||
Martha.mood: 0.6
|
||
}
|
||
on energy < 0.2 -> exhausted
|
||
on energy > 0.7 -> rested
|
||
}
|
||
|
||
state exhausted {
|
||
on enter {
|
||
Martha.mood: 0.3
|
||
Martha.quality_output: 0.7
|
||
}
|
||
on energy > 0.5 -> tired
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="relationships-2"><a class="header" href="#relationships-2">Relationships</a></h2>
|
||
<pre><code class="language-storybook">relationship MarthaAndApprentice {
|
||
Martha as mentor self {
|
||
patience: 0.8
|
||
investment: 0.9
|
||
} other {
|
||
sees_potential: 0.85
|
||
}
|
||
|
||
Elena as apprentice self {
|
||
dedication: 0.9
|
||
learning_rate: 0.7
|
||
} other {
|
||
respect: 0.95
|
||
admiration: 0.8
|
||
}
|
||
|
||
bond: 0.85
|
||
years_together: 2
|
||
}
|
||
|
||
relationship MarthaAndRegularCustomer {
|
||
Martha as shopkeeper
|
||
OldManGregory as regular_customer
|
||
|
||
bond: 0.7
|
||
years_known: 15
|
||
always_orders: "sourdough_loaf"
|
||
|
||
---dynamics
|
||
Gregory has been buying Martha's bread every morning for
|
||
fifteen years. They exchange brief pleasantries about the
|
||
weather and local gossip. He is her most reliable customer.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="key-takeaways-2"><a class="header" href="#key-takeaways-2">Key Takeaways</a></h2>
|
||
<p>This example demonstrates:</p>
|
||
<ol>
|
||
<li><strong>Schedule-driven daily flow</strong>: Precise time blocks govern Martha’s entire day</li>
|
||
<li><strong>Seasonal and weekly variations</strong>: Summer hours and Saturday market</li>
|
||
<li><strong>Layered behaviors</strong>: Emergency behavior preempts normal routine via priority</li>
|
||
<li><strong>Realistic action sequences</strong>: Baking modeled step by step with parameters</li>
|
||
<li><strong>Energy management</strong>: Life arc tracks fatigue affecting mood and output quality</li>
|
||
<li><strong>Social connections</strong>: Relationships with apprentice and customers add depth</li>
|
||
</ol>
|
||
<h2 id="cross-references-17"><a class="header" href="#cross-references-17">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="examples/../reference/14-schedules.html">Schedules Reference</a> - Schedule syntax</li>
|
||
<li><a href="examples/../reference/11-behavior-trees.html">Behavior Trees Reference</a> - Behavior syntax</li>
|
||
<li><a href="examples/../reference/13-life-arcs.html">Life Arcs Reference</a> - Life arc syntax</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="character-evolution"><a class="header" href="#character-evolution">Character Evolution</a></h1>
|
||
<p>This example models a character who evolves through multiple life stages, demonstrating how life arcs, behavior trees, and templates work together to represent growth over time.</p>
|
||
<h2 id="the-apprentices-journey"><a class="header" href="#the-apprentices-journey">The Apprentice’s Journey</a></h2>
|
||
<p>Elena starts as a nervous apprentice and grows into a confident master baker. Her evolution touches every aspect of her character: skills, personality, relationships, and daily routine.</p>
|
||
<h3 id="schema"><a class="header" href="#schema">Schema</a></h3>
|
||
<pre><code class="language-storybook">enum SkillLevel { novice, beginner, intermediate, advanced, expert, master }
|
||
|
||
enum Confidence { timid, uncertain, growing, steady, confident, commanding }
|
||
|
||
template Apprentice {
|
||
skill_level: novice
|
||
confidence: timid
|
||
can_work_independently: false
|
||
recipes_mastered: 0..5
|
||
}
|
||
|
||
template Journeyman {
|
||
skill_level: intermediate
|
||
confidence: growing
|
||
can_work_independently: true
|
||
recipes_mastered: 10..30
|
||
}
|
||
|
||
template MasterBaker {
|
||
skill_level: master
|
||
confidence: commanding
|
||
can_work_independently: true
|
||
can_teach: true
|
||
recipes_mastered: 50..200
|
||
}
|
||
</code></pre>
|
||
<h3 id="the-character-at-different-stages"><a class="header" href="#the-character-at-different-stages">The Character at Different Stages</a></h3>
|
||
<pre><code class="language-storybook">// Elena starts as an apprentice
|
||
character Elena: Human from Apprentice {
|
||
age: 16
|
||
natural_talent: 0.8
|
||
dedication: 0.9
|
||
recipes_mastered: 2
|
||
confidence: timid
|
||
mentor: Martha
|
||
|
||
---backstory
|
||
Elena comes from a family of farmers who could never afford to
|
||
buy bread from the bakery. When Martha offered her an apprenticeship,
|
||
she jumped at the chance to learn a trade.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h3 id="the-evolution-life-arc"><a class="header" href="#the-evolution-life-arc">The Evolution Life Arc</a></h3>
|
||
<pre><code class="language-storybook">life_arc ElenaCareer {
|
||
---description
|
||
Tracks Elena's progression from nervous apprentice to
|
||
confident master baker over several years.
|
||
---
|
||
|
||
state apprentice_early {
|
||
on enter {
|
||
Elena.skill_level: novice
|
||
Elena.confidence: timid
|
||
Elena.can_work_independently: false
|
||
}
|
||
|
||
on recipes_mastered > 5 -> apprentice_growing
|
||
|
||
---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 apprentice_growing {
|
||
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 > 30 and confidence is steady -> senior_journeyman
|
||
|
||
---narrative
|
||
Elena runs the morning shift alone while Martha handles
|
||
special orders. Customers start asking for "Elena's rolls."
|
||
She begins experimenting with her own recipes.
|
||
---
|
||
}
|
||
|
||
state senior_journeyman {
|
||
on enter {
|
||
Elena.skill_level: advanced
|
||
Elena.confidence: steady
|
||
}
|
||
|
||
on recipes_mastered > 50 and passed_master_trial -> master
|
||
|
||
---narrative
|
||
Elena develops her signature recipe: rosemary olive bread
|
||
that becomes the bakery's bestseller. She handles difficult
|
||
customers with grace and trains new helpers.
|
||
---
|
||
}
|
||
|
||
state master {
|
||
on enter {
|
||
Elena.skill_level: master
|
||
Elena.confidence: commanding
|
||
Elena.can_teach: true
|
||
}
|
||
|
||
---narrative
|
||
Master Baker Elena. She has earned it. The guild acknowledges
|
||
her mastery, and Martha beams with pride. Elena begins
|
||
mentoring her own apprentice.
|
||
---
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="evolving-behaviors"><a class="header" href="#evolving-behaviors">Evolving Behaviors</a></h3>
|
||
<p>Elena’s behavior changes as she progresses:</p>
|
||
<pre><code class="language-storybook">// Early apprentice: hesitant, checks everything
|
||
behavior Elena_ApprenticeEarly {
|
||
then cautious_baking {
|
||
CheckRecipeThreeTimes
|
||
MeasureCarefully
|
||
AskMarthaForConfirmation
|
||
ProceedSlowly
|
||
CheckResultAnxiously
|
||
}
|
||
}
|
||
|
||
// Growing apprentice: more confident
|
||
behavior Elena_ApprenticeGrowing {
|
||
then competent_baking {
|
||
ReviewRecipe
|
||
MeasureIngredients
|
||
MixWithConfidence
|
||
choose problem_handling {
|
||
then handle_alone {
|
||
if(confidence > 0.4)
|
||
AssessSituation
|
||
ApplyLearning
|
||
}
|
||
AskMarthaForHelp
|
||
}
|
||
}
|
||
}
|
||
|
||
// Journeyman: independent and creative
|
||
behavior Elena_Journeyman {
|
||
choose work_mode {
|
||
then creative_mode {
|
||
if(inspiration_high)
|
||
ExperimentWithRecipe
|
||
TasteTest
|
||
if(result_good) {
|
||
RecordNewRecipe
|
||
}
|
||
}
|
||
|
||
then production_mode {
|
||
ExecuteRecipeFromMemory
|
||
MonitorOvenTimings
|
||
ManageMultipleBatches
|
||
}
|
||
|
||
then teaching_mode {
|
||
if(helper_present)
|
||
DemonstrateTeechnique
|
||
ObserveHelper
|
||
ProvideGentleFeedback
|
||
}
|
||
}
|
||
}
|
||
|
||
// Master: leadership and mentoring
|
||
behavior Elena_Master {
|
||
choose master_activity {
|
||
then mentor_apprentice {
|
||
if(apprentice_needs_guidance)
|
||
AssessApprenticeProgress
|
||
DesignLearningChallenge
|
||
ObserveAndFeedback
|
||
}
|
||
|
||
then innovate {
|
||
if(creative_energy_high)
|
||
ResearchNewTechniques
|
||
ExperimentWithIngredients
|
||
DocumentFindings
|
||
}
|
||
|
||
then lead_production {
|
||
PlanDailyProduction
|
||
DelegateToTeam
|
||
QualityCheckResults
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="evolving-relationships"><a class="header" href="#evolving-relationships">Evolving Relationships</a></h3>
|
||
<p>The mentor relationship changes as Elena grows:</p>
|
||
<pre><code class="language-storybook">// Early apprenticeship
|
||
relationship EarlyMentorship {
|
||
Martha as mentor self {
|
||
patience: 0.9
|
||
teaching_intensity: 0.8
|
||
} other {
|
||
sees_potential: 0.8
|
||
reminds_her_of_herself: true
|
||
}
|
||
|
||
Elena as apprentice self {
|
||
gratitude: 1.0
|
||
anxiety: 0.7
|
||
} other {
|
||
admiration: 0.95
|
||
intimidated: 0.5
|
||
}
|
||
|
||
bond: 0.6
|
||
}
|
||
|
||
// Later: colleagues and friends
|
||
relationship MaturePartnership {
|
||
Martha as senior_partner self {
|
||
pride_in_elena: 0.95
|
||
ready_to_step_back: 0.6
|
||
} other {
|
||
sees_equal: 0.8
|
||
trusts_judgment: 0.9
|
||
}
|
||
|
||
Elena as junior_partner self {
|
||
confidence: 0.85
|
||
gratitude: 0.9
|
||
} other {
|
||
respect: 0.95
|
||
sees_as_mother_figure: 0.7
|
||
}
|
||
|
||
bond: 0.95
|
||
}
|
||
</code></pre>
|
||
<h3 id="evolving-schedules"><a class="header" href="#evolving-schedules">Evolving Schedules</a></h3>
|
||
<p>Elena’s schedule changes as she takes on more responsibility:</p>
|
||
<pre><code class="language-storybook">// Apprentice schedule: supervised hours
|
||
schedule ElenaApprentice {
|
||
block arrive {
|
||
06:00 - 06:15
|
||
action: routines::arrive_early
|
||
}
|
||
|
||
block learn_and_assist {
|
||
06:15 - 14:00
|
||
action: baking::assist_martha
|
||
}
|
||
|
||
block cleanup_duty {
|
||
14:00 - 15:00
|
||
action: shop::cleanup
|
||
}
|
||
|
||
block study {
|
||
15:00 - 16:00
|
||
action: learning::study_recipes
|
||
}
|
||
}
|
||
|
||
// Master schedule: leadership hours
|
||
schedule ElenaMaster extends ElenaApprentice {
|
||
block arrive {
|
||
04:00 - 04:15
|
||
action: routines::open_bakery
|
||
}
|
||
|
||
block learn_and_assist {
|
||
04:15 - 12:00
|
||
action: baking::lead_production
|
||
}
|
||
|
||
block cleanup_duty {
|
||
12:00 - 13:00
|
||
action: social::lunch_with_team
|
||
}
|
||
|
||
block study {
|
||
13:00 - 15:00
|
||
action: baking::mentor_apprentice
|
||
}
|
||
|
||
block business {
|
||
15:00 - 17:00
|
||
action: management::business_planning
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="key-takeaways-3"><a class="header" href="#key-takeaways-3">Key Takeaways</a></h2>
|
||
<p>This example demonstrates:</p>
|
||
<ol>
|
||
<li><strong>Life arcs as character development</strong>: Elena’s career progression modeled as states</li>
|
||
<li><strong>Evolving behaviors</strong>: Different behavior trees for each stage of growth</li>
|
||
<li><strong>Changing relationships</strong>: The mentor dynamic shifts from dependency to partnership</li>
|
||
<li><strong>Schedule evolution</strong>: Responsibilities grow with skill level</li>
|
||
<li><strong>Narrative prose</strong>: Each life arc state tells a story about who Elena is becoming</li>
|
||
<li><strong>Template progression</strong>: Templates define the capability profile at each stage</li>
|
||
</ol>
|
||
<h2 id="cross-references-18"><a class="header" href="#cross-references-18">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="examples/../reference/13-life-arcs.html">Life Arcs Reference</a> - State machine syntax</li>
|
||
<li><a href="examples/../advanced/20-patterns.html">Design Patterns</a> - Progressive development pattern</li>
|
||
<li><a href="examples/../advanced/23-best-practices.html">Best Practices</a> - Character design guidelines</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="multi-character-interactions"><a class="header" href="#multi-character-interactions">Multi-Character Interactions</a></h1>
|
||
<p>This example models a complex social scene: a busy Saturday morning at Martha’s bakery. Multiple characters interact simultaneously with interlocking behaviors, relationships, and a shared location buzzing with activity.</p>
|
||
<h2 id="the-setting"><a class="header" href="#the-setting">The Setting</a></h2>
|
||
<pre><code class="language-storybook">enum RushLevel { calm, busy, hectic, overwhelming }
|
||
enum ServiceMode { normal, rush, emergency }
|
||
|
||
location BakeryStorefront {
|
||
rush_level: busy
|
||
current_time: 07:30
|
||
customers_waiting: 8
|
||
display_items_remaining: 45
|
||
oven_batches_in_progress: 3
|
||
coffee_machine_running: true
|
||
|
||
---description
|
||
Saturday morning at Martha's bakery. The line stretches out
|
||
the door. The display case gleams with fresh bread, pastries,
|
||
and Elena's famous rosemary olive rolls. The air is warm with
|
||
the smell of baking and the hum of conversation.
|
||
---
|
||
|
||
---atmosphere
|
||
This is the bakery at its best and most stressful. Every
|
||
Saturday brings the regulars, the farmers' market overflow,
|
||
and tourists who heard about Martha's sourdough. The whole
|
||
team works in concert to keep up.
|
||
---
|
||
}
|
||
|
||
institution SaturdayMorningCrew {
|
||
type: work_team
|
||
purpose: serve_customers_and_bake
|
||
members: 4
|
||
coordination_level: 0.9
|
||
|
||
---description
|
||
The Saturday crew operates like a well-oiled machine. Martha
|
||
runs the kitchen, Jane handles pastries, Elena manages the
|
||
front counter, and Gregory -- the loyal regular -- unofficially
|
||
helps direct the line.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="the-characters"><a class="header" href="#the-characters">The Characters</a></h2>
|
||
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Confidence, Specialty};
|
||
use schema::templates::{Baker, BusinessOwner, Apprentice};
|
||
use schema::beings::Human;
|
||
|
||
character Martha: Human from Baker, BusinessOwner {
|
||
age: 34
|
||
specialty: sourdough
|
||
skill_level: master
|
||
confidence: commanding
|
||
energy: 0.8
|
||
stress_level: 0.4
|
||
loaves_baked_today: 24
|
||
orders_pending: 6
|
||
|
||
---personality
|
||
Calm under pressure. Martha thrives on Saturday mornings --
|
||
the rush brings out her best. She coordinates the team with
|
||
quiet efficiency, stepping in wherever needed while keeping
|
||
the ovens running on schedule.
|
||
---
|
||
}
|
||
|
||
character Jane: Human from Baker {
|
||
age: 36
|
||
specialty: pastries
|
||
skill_level: expert
|
||
confidence: confident
|
||
energy: 0.9
|
||
creative_mode: true
|
||
pastries_decorated_today: 18
|
||
|
||
---personality
|
||
Jane works in focused silence during the rush. Her hands
|
||
move with precision, piping decorations and assembling
|
||
layered pastries. She communicates with Martha through
|
||
glances and nods -- years of partnership have made words
|
||
unnecessary.
|
||
---
|
||
}
|
||
|
||
character Elena: Human from Apprentice {
|
||
age: 17
|
||
skill_level: intermediate
|
||
confidence: growing
|
||
energy: 1.0
|
||
customers_served_today: 32
|
||
mistakes_today: 1
|
||
|
||
---personality
|
||
Elena has grown into the front-counter role. She remembers
|
||
regulars' names and orders, handles complaints with grace,
|
||
and only calls Martha when truly stuck. The nervous girl
|
||
who started a year ago is barely recognizable.
|
||
---
|
||
}
|
||
|
||
character Gregory: Human {
|
||
age: 68
|
||
role: "regular_customer"
|
||
visits_today: 1
|
||
helping_with_line: true
|
||
knows_everyone: true
|
||
|
||
---personality
|
||
Gregory arrives at exactly 7:15 every Saturday. He buys
|
||
his sourdough loaf, then lingers near the door, chatting
|
||
with other customers and unofficially managing the line.
|
||
He considers this his contribution to the bakery.
|
||
---
|
||
}
|
||
</code></pre>
|
||
<h2 id="interlocking-behaviors"><a class="header" href="#interlocking-behaviors">Interlocking Behaviors</a></h2>
|
||
<h3 id="marthas-behavior"><a class="header" href="#marthas-behavior">Martha’s Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Martha_SaturdayMorning {
|
||
---description
|
||
Martha's Saturday morning routine: managing the kitchen,
|
||
coordinating the team, and keeping the ovens running.
|
||
---
|
||
|
||
repeat {
|
||
choose saturday_priority {
|
||
// Check ovens first (highest priority)
|
||
then oven_management {
|
||
if(oven_timer_near_done)
|
||
CheckOvenTemperature
|
||
RemoveFinishedBatch
|
||
LoadNextBatch
|
||
SetTimer
|
||
}
|
||
|
||
// Handle special orders
|
||
then special_orders {
|
||
if(has_special_orders)
|
||
choose order_type {
|
||
PrepareWeddingCake
|
||
BoxCustomOrder
|
||
DecorateSpecialLoaf
|
||
}
|
||
}
|
||
|
||
// Support Elena at counter
|
||
then help_counter {
|
||
if(elena_needs_help)
|
||
choose counter_support {
|
||
AnswerCustomerQuestion
|
||
HandleComplaint
|
||
ProcessLargeOrder
|
||
}
|
||
}
|
||
|
||
// Coordinate with Jane
|
||
then coordinate_pastries {
|
||
if(display_items_remaining < 10)
|
||
SignalJaneToRestockPastries
|
||
RearrangeDisplay
|
||
}
|
||
|
||
// Default: knead next batch
|
||
then prep_dough {
|
||
MixNextBatch
|
||
KneadDough
|
||
ShapeLoaves
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="janes-behavior"><a class="header" href="#janes-behavior">Jane’s Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Jane_SaturdayMorning {
|
||
repeat {
|
||
choose jane_priority {
|
||
// Restock display when signaled
|
||
then restock_pastries {
|
||
if(martha_signaled_restock)
|
||
PlateFinishedPastries
|
||
CarryToDisplay
|
||
ArrangeAttractively
|
||
}
|
||
|
||
// Decorate current batch
|
||
then decorating {
|
||
if(has_undecorated_pastries)
|
||
PipeIcing
|
||
AddGarnish
|
||
InspectQuality
|
||
}
|
||
|
||
// Start new pastry batch
|
||
then new_batch {
|
||
if(pastry_dough_ready)
|
||
RollPastryDough
|
||
CutShapes
|
||
AddFilling
|
||
PlaceOnBakingSheet
|
||
}
|
||
|
||
// Prepare specialty items
|
||
then specialty_items {
|
||
if(specialty_order_pending)
|
||
ReviewOrderNotes
|
||
SelectPremiumIngredients
|
||
CraftSpecialtyItem
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="elenas-behavior"><a class="header" href="#elenas-behavior">Elena’s Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Elena_SaturdayCounter {
|
||
choose counter_state {
|
||
// Serve waiting customers
|
||
then serve_customer {
|
||
if(customer_waiting)
|
||
then service_sequence {
|
||
GreetCustomer
|
||
if(customer_is_regular) {
|
||
RecallPreferences
|
||
}
|
||
|
||
choose order_handling {
|
||
then quick_order {
|
||
if(customer_knows_what_they_want)
|
||
AcceptOrder
|
||
PackageItem
|
||
}
|
||
|
||
then help_decide {
|
||
if(not customer_knows_what_they_want)
|
||
OfferRecommendation
|
||
OfferSample
|
||
AcceptOrder
|
||
PackageItem
|
||
}
|
||
}
|
||
|
||
CollectPayment
|
||
ThankCustomer
|
||
}
|
||
}
|
||
|
||
// Handle problems
|
||
then handle_issue {
|
||
if(customer_has_complaint)
|
||
choose resolution {
|
||
then resolve_alone {
|
||
if(confidence > 0.5)
|
||
ListenCarefully
|
||
OfferSolution
|
||
ApplyResolution
|
||
}
|
||
|
||
then escalate {
|
||
if(confidence <= 0.5)
|
||
AcknowledgeProblem
|
||
CallMarthaForHelp
|
||
}
|
||
}
|
||
}
|
||
|
||
// Manage the line
|
||
then manage_queue {
|
||
if(line_length > 5)
|
||
AnnounceWaitTime
|
||
SuggestPopularItems
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="gregorys-behavior"><a class="header" href="#gregorys-behavior">Gregory’s Behavior</a></h3>
|
||
<pre><code class="language-storybook">behavior Gregory_SaturdayVisit {
|
||
then saturday_routine {
|
||
// Arrive and order
|
||
then arrival {
|
||
EnterBakery
|
||
GreetElena
|
||
OrderSourdoughLoaf
|
||
PayExactChange
|
||
}
|
||
|
||
// Linger and help
|
||
choose lingering_activity {
|
||
then manage_line {
|
||
if(line_is_long)
|
||
DirectNewCustomersToEndOfLine
|
||
ChatWithWaitingCustomers
|
||
RecommendPopularItems
|
||
}
|
||
|
||
then catch_up {
|
||
if(sees_familiar_face)
|
||
GreetNeighbor
|
||
ExchangeLocalNews
|
||
DiscussWeather
|
||
}
|
||
|
||
then observe_elena {
|
||
if(elena_handling_difficult_customer)
|
||
StandNearbyForMoralSupport
|
||
NodEncouragingly
|
||
}
|
||
}
|
||
|
||
// Eventually leave
|
||
then departure {
|
||
WaveToMartha
|
||
SayGoodbyeToElena
|
||
ExitWithBread
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="relationships-3"><a class="header" href="#relationships-3">Relationships</a></h2>
|
||
<pre><code class="language-storybook">relationship BakeryPartnership {
|
||
Martha {
|
||
role: co_owner
|
||
coordination: 1.0
|
||
handles_bread: true
|
||
|
||
---perspective
|
||
Martha and Jane communicate without words during the rush.
|
||
A glance toward the display case means "we're running low."
|
||
A nod means "I'll handle it." Years of working side by side
|
||
have created an effortless rhythm.
|
||
---
|
||
}
|
||
|
||
Jane {
|
||
role: co_owner
|
||
coordination: 1.0
|
||
handles_pastries: true
|
||
|
||
---perspective
|
||
Jane trusts Martha's judgment completely during the Saturday
|
||
rush. If Martha signals, Jane reprioritizes. If Jane needs
|
||
oven time, Martha adjusts. They are two halves of a single
|
||
well-run kitchen.
|
||
---
|
||
}
|
||
|
||
bond: 0.95
|
||
}
|
||
|
||
relationship TeamAndApprentice {
|
||
Martha as mentor
|
||
Jane as senior_colleague
|
||
Elena as apprentice
|
||
|
||
bond: 0.8
|
||
|
||
---dynamics
|
||
Elena looks up to both Martha and Jane, but in different ways.
|
||
Martha teaches her the fundamentals -- technique, discipline,
|
||
consistency. Jane shows her the creative side -- decoration,
|
||
presentation, flavor combinations. Together they are shaping
|
||
Elena into a complete baker.
|
||
---
|
||
}
|
||
|
||
relationship GregoryAtTheBakery {
|
||
Gregory {
|
||
role: loyal_customer
|
||
attachment: 0.9
|
||
unofficial_helper: true
|
||
|
||
---perspective
|
||
The bakery is Gregory's third place -- not home, not the
|
||
library where he used to teach, but the warm space where
|
||
he belongs. He has watched Elena grow from a nervous girl
|
||
to a confident young woman. He is proud, though he would
|
||
never say so directly.
|
||
---
|
||
}
|
||
|
||
Elena {
|
||
role: counter_staff
|
||
fondness: 0.8
|
||
sees_as: "grandfather_figure"
|
||
|
||
---perspective
|
||
Elena looks forward to Gregory's arrival every morning.
|
||
His exact-change payment and dry humor are a reliable
|
||
anchor in the chaos of the morning rush.
|
||
---
|
||
}
|
||
|
||
bond: 0.7
|
||
}
|
||
</code></pre>
|
||
<h2 id="the-saturday-schedule"><a class="header" href="#the-saturday-schedule">The Saturday Schedule</a></h2>
|
||
<pre><code class="language-storybook">schedule SaturdayRush {
|
||
block early_prep {
|
||
03:00 - 06:00
|
||
action: baking::saturday_batch
|
||
}
|
||
|
||
block opening {
|
||
06:00 - 06:15
|
||
action: shop::open_doors
|
||
}
|
||
|
||
block morning_rush {
|
||
06:15 - 11:00
|
||
action: shop::saturday_rush_service
|
||
}
|
||
|
||
block midday_restock {
|
||
11:00 - 12:00
|
||
action: baking::midday_supplemental
|
||
}
|
||
|
||
block afternoon_wind_down {
|
||
12:00 - 14:00
|
||
action: shop::afternoon_sales
|
||
}
|
||
|
||
block close_and_clean {
|
||
14:00 - 15:00
|
||
action: shop::saturday_cleanup
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="life-arc-elenas-saturday-confidence"><a class="header" href="#life-arc-elenas-saturday-confidence">Life Arc: Elena’s Saturday Confidence</a></h2>
|
||
<pre><code class="language-storybook">life_arc ElenaSaturdayGrowth {
|
||
state nervous_start {
|
||
on enter {
|
||
Elena.confidence: uncertain
|
||
Elena.energy: 1.0
|
||
}
|
||
on customers_served_today > 5 -> finding_rhythm
|
||
|
||
---narrative
|
||
The first few customers are always the hardest. Elena
|
||
fumbles with the register, second-guesses prices, and
|
||
looks to Martha for confirmation. But each successful
|
||
transaction builds her up.
|
||
---
|
||
}
|
||
|
||
state finding_rhythm {
|
||
on enter {
|
||
Elena.confidence: growing
|
||
}
|
||
on customers_served_today > 15 -> in_the_zone
|
||
|
||
---narrative
|
||
Something clicks. Elena stops thinking about each step
|
||
and starts flowing. She remembers Mrs. Patterson's usual
|
||
order before she says it. She bags the croissants without
|
||
looking.
|
||
---
|
||
}
|
||
|
||
state in_the_zone {
|
||
on enter {
|
||
Elena.confidence: confident
|
||
}
|
||
on handled_complaint_alone -> proud_moment
|
||
on energy < 0.3 -> running_on_fumes
|
||
|
||
---narrative
|
||
Elena is running the counter like she was born to it.
|
||
Gregory gives her a quiet nod of approval from his spot
|
||
by the door. She barely notices -- she is too busy being
|
||
competent.
|
||
---
|
||
}
|
||
|
||
state proud_moment {
|
||
on enter {
|
||
Elena.confidence: confident
|
||
Elena.self_respect: 0.9
|
||
}
|
||
|
||
---narrative
|
||
A customer complained about a stale roll. Elena apologized,
|
||
replaced it with a fresh one, and offered a free cookie.
|
||
The customer left smiling. Elena handled it alone, without
|
||
calling Martha. She stands a little taller afterward.
|
||
---
|
||
}
|
||
|
||
state running_on_fumes {
|
||
on enter {
|
||
Elena.energy: 0.2
|
||
Elena.confidence: uncertain
|
||
}
|
||
on break_taken -> finding_rhythm
|
||
|
||
---narrative
|
||
The rush has been going for four hours. Elena's smile
|
||
is getting harder to maintain. Martha notices and sends
|
||
her to the back for a five-minute break and a pastry.
|
||
---
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="key-takeaways-4"><a class="header" href="#key-takeaways-4">Key Takeaways</a></h2>
|
||
<p>This example demonstrates:</p>
|
||
<ol>
|
||
<li><strong>Multiple characters with interlocking behaviors</strong>: Martha, Jane, Elena, and Gregory react to each other</li>
|
||
<li><strong>Character coordination</strong>: Martha and Jane operate as a seamless team</li>
|
||
<li><strong>Asymmetric group dynamics</strong>: Gregory is an unofficial helper, Elena is growing into her role</li>
|
||
<li><strong>Location as context</strong>: The busy bakery storefront defines the scene</li>
|
||
<li><strong>Institution modeling</strong>: The Saturday crew as a coordinated work team</li>
|
||
<li><strong>Visitor arc</strong>: Elena’s confidence through the Saturday rush modeled as a life arc</li>
|
||
<li><strong>Rich prose</strong>: Every character and relationship includes narrative perspective</li>
|
||
</ol>
|
||
<h2 id="cross-references-19"><a class="header" href="#cross-references-19">Cross-References</a></h2>
|
||
<ul>
|
||
<li><a href="examples/../reference/15-relationships.html">Relationships Reference</a> - Multi-party relationships</li>
|
||
<li><a href="examples/../reference/11-behavior-trees.html">Behavior Trees Reference</a> - Coordinated behaviors</li>
|
||
<li><a href="examples/../reference/13-life-arcs.html">Life Arcs Reference</a> - Scene-based state machines</li>
|
||
<li><a href="examples/./24-baker-family-complete.html">Baker Family Complete</a> - Full project context</li>
|
||
</ul>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
|
||
</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 -->
|
||
|
||
<script>
|
||
window.addEventListener('load', function() {
|
||
window.setTimeout(window.print, 100);
|
||
});
|
||
</script>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|