Files
storybook/docs/book/tutorial/08-life-arcs.html
Sienna Meridian Satterwhite 16deb5d237 release: Storybook v0.2.0 - Major syntax and features update
BREAKING CHANGES:
- Relationship syntax now requires blocks for all participants
- Removed self/other perspective blocks from relationships
- Replaced 'guard' keyword with 'if' for behavior tree decorators

Language Features:
- Add tree-sitter grammar with improved if/condition disambiguation
- Add comprehensive tutorial and reference documentation
- Add SBIR v0.2.0 binary format specification
- Add resource linking system for behaviors and schedules
- Add year-long schedule patterns (day, season, recurrence)
- Add behavior tree enhancements (named nodes, decorators)

Documentation:
- Complete tutorial series (9 chapters) with baker family examples
- Complete reference documentation for all language features
- SBIR v0.2.0 specification with binary format details
- Added locations and institutions documentation

Examples:
- Convert all examples to baker family scenario
- Add comprehensive working examples

Tooling:
- Zed extension with LSP integration
- Tree-sitter grammar for syntax highlighting
- Build scripts and development tools

Version Updates:
- Main package: 0.1.0 → 0.2.0
- Tree-sitter grammar: 0.1.0 → 0.2.0
- Zed extension: 0.1.0 → 0.2.0
- Storybook editor: 0.1.0 → 0.2.0
2026-02-13 21:52:03 +00:00

452 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Life Arcs - Storybook Language Guide</title>
<!-- Custom HTML head -->
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Storybook Language Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="life-arcs"><a class="header" href="#life-arcs">Life Arcs</a></h1>
<p>Characters grow and change. A timid apprentice becomes a confident master baker. A new employee finds their place in the team. Life arcs model these transformations as state machines discrete phases with conditions that trigger transitions between them.</p>
<h2 id="what-is-a-life-arc"><a class="header" href="#what-is-a-life-arc">What is a Life Arc?</a></h2>
<p>A life arc defines:</p>
<ul>
<li><strong>States</strong>: Distinct phases (e.g., “apprentice”, “journeyman”, “master”)</li>
<li><strong>Transitions</strong>: Conditions that move between states (e.g., “when skill &gt; 80, become master”)</li>
<li><strong>On-enter actions</strong>: Changes that happen when entering a state (e.g., set confidence to 0.9)</li>
</ul>
<h2 id="your-first-life-arc"><a class="header" href="#your-first-life-arc">Your First Life Arc</a></h2>
<pre><code class="language-storybook">life_arc BakerCareer {
state apprentice {
on enter {
Baker.skill_level: 0.2
Baker.confidence: 0.3
}
on skill_level &gt; 0.5 -&gt; journeyman
}
state journeyman {
on enter {
Baker.confidence: 0.6
}
on skill_level &gt; 0.8 -&gt; master
}
state master {
on enter {
Baker.confidence: 0.95
Baker.can_teach: true
}
}
}
</code></pre>
<p>Reading this as a story:</p>
<blockquote>
<p>The baker starts as an <strong>apprentice</strong> with low skill and confidence. When skill exceeds 0.5, they become a <strong>journeyman</strong> with improved confidence. When skill exceeds 0.8, they become a <strong>master</strong> who is confident and can teach others.</p>
</blockquote>
<h2 id="states"><a class="header" href="#states">States</a></h2>
<p>Each state represents a distinct phase. States contain:</p>
<ul>
<li><strong>on enter block</strong>: Field updates that happen when entering the state</li>
<li><strong>Transitions</strong>: Conditions that trigger moving to another state</li>
<li><strong>Prose blocks</strong>: Narrative descriptions</li>
</ul>
<pre><code class="language-storybook">state early_apprentice {
on enter {
Elena.skill_level: novice
Elena.confidence: timid
}
on recipes_mastered &gt; 5 -&gt; growing_apprentice
on quit_apprenticeship -&gt; former_apprentice
---narrative
Elena's hands shake as she measures flour. She checks the
recipe three times before adding each ingredient. Martha
patiently corrects her technique.
---
}
</code></pre>
<h2 id="transitions"><a class="header" href="#transitions">Transitions</a></h2>
<p>Transitions use expressions to decide when to change state:</p>
<pre><code class="language-storybook">// Simple conditions
on health &lt; 20 -&gt; fleeing
on quest_complete -&gt; celebrating
// Boolean fields
on is_hostile -&gt; combat
on not ready -&gt; waiting
// Equality checks
on status is active -&gt; active_state
on name is "Martha" -&gt; found_martha
// Complex conditions
on health &lt; 50 and enemy_count &gt; 3 -&gt; retreat
on (tired and hungry) or exhausted -&gt; resting
</code></pre>
<h3 id="transition-priority"><a class="header" href="#transition-priority">Transition Priority</a></h3>
<p>When multiple transitions could fire, the <strong>first one in declaration order</strong> wins:</p>
<pre><code class="language-storybook">state combat {
on health &lt; 10 -&gt; desperate // Checked first
on health &lt; 50 -&gt; defensive // Checked second
on surrounded -&gt; retreat // Checked third
}
</code></pre>
<p>If health is 5, only <code>desperate</code> triggers, even though <code>defensive</code> is also true.</p>
<h2 id="on-enter-actions"><a class="header" href="#on-enter-actions">On-Enter Actions</a></h2>
<p>The <code>on enter</code> block sets field values when entering a state. It runs exactly once per transition:</p>
<pre><code class="language-storybook">state master_baker {
on enter {
Elena.skill_level: master
Elena.confidence: commanding
Elena.can_teach: true
}
}
</code></pre>
<p>The syntax is <code>EntityName.field: value</code>. This updates the referenced characters field.</p>
<h2 id="elenas-complete-journey"><a class="header" href="#elenas-complete-journey">Elenas Complete Journey</a></h2>
<p>Here is a complete life arc tracking Elenas growth from apprentice to master:</p>
<pre><code class="language-storybook">life_arc ElenaCareer {
---description
Tracks Elena's professional and personal growth as she
progresses from nervous apprentice to confident master baker.
---
state early_apprentice {
on enter {
Elena.skill_level: novice
Elena.confidence: timid
}
on recipes_mastered &gt; 5 -&gt; growing_apprentice
---narrative
Elena's hands shake as she measures flour. She checks the
recipe three times before adding each ingredient. Martha
patiently corrects her technique.
---
}
state growing_apprentice {
on enter {
Elena.skill_level: beginner
Elena.confidence: uncertain
}
on recipes_mastered &gt; 15 -&gt; journeyman
---narrative
The shaking stops. Elena can make basic breads without
looking at the recipe. She still doubts herself but
Martha's encouragement is taking root.
---
}
state journeyman {
on enter {
Elena.skill_level: intermediate
Elena.confidence: growing
Elena.can_work_independently: true
}
on recipes_mastered &gt; 50 -&gt; master
---narrative
Elena runs the morning shift alone while Martha handles
special orders. Customers start asking for "Elena's rolls."
---
}
state master {
on enter {
Elena.skill_level: master
Elena.confidence: commanding
Elena.can_teach: true
}
---narrative
Master Baker Elena. She has earned it. The guild acknowledges
her mastery, and Martha beams with pride. Elena begins
mentoring her own apprentice.
---
}
}
</code></pre>
<h2 id="common-patterns"><a class="header" href="#common-patterns">Common Patterns</a></h2>
<h3 id="hub-and-spoke"><a class="header" href="#hub-and-spoke">Hub-and-Spoke</a></h3>
<p>A central idle state with branches to specialized states:</p>
<pre><code class="language-storybook">life_arc BakerBehavior {
state idle {
on customer_arrived -&gt; serving
on order_placed -&gt; baking
on delivery_arrived -&gt; receiving
}
state serving { on customer_served -&gt; idle }
state baking { on batch_complete -&gt; idle }
state receiving { on delivery_processed -&gt; idle }
}
</code></pre>
<h3 id="linear-progression"><a class="header" href="#linear-progression">Linear Progression</a></h3>
<p>States form a one-way sequence (quests, tutorials):</p>
<pre><code class="language-storybook">life_arc Tutorial {
state intro { on clicked_start -&gt; movement }
state movement { on moved_forward -&gt; combat }
state combat { on defeated_enemy -&gt; inventory }
state inventory { on opened_inventory -&gt; complete }
state complete {}
}
</code></pre>
<h3 id="cyclic-states"><a class="header" href="#cyclic-states">Cyclic States</a></h3>
<p>States form a loop (day/night, seasons, mood swings):</p>
<pre><code class="language-storybook">life_arc DayNightCycle {
state dawn { on hour &gt;= 8 -&gt; day }
state day { on hour &gt;= 18 -&gt; dusk }
state dusk { on hour &gt;= 20 -&gt; night }
state night { on hour &gt;= 6 -&gt; dawn }
}
</code></pre>
<h2 id="tips"><a class="header" href="#tips">Tips</a></h2>
<p><strong>Order transitions by urgency</strong>: Put the most critical conditions first, since the first true transition wins.</p>
<p><strong>Use descriptive state names</strong>: <code>waiting_for_customer</code> is clearer than <code>state1</code>.</p>
<p><strong>Add narrative prose blocks</strong>: They make life arcs readable as stories and serve as documentation.</p>
<p><strong>Avoid orphan states</strong>: Every state should be reachable from some other state (the compiler will warn you about unreachable states).</p>
<h2 id="what-you-have-learned"><a class="header" href="#what-you-have-learned">What You Have Learned</a></h2>
<p>Congratulations! You have completed the tutorial. You now know how to:</p>
<ul>
<li>Create characters with species, templates, and enums</li>
<li>Build behavior trees with selectors, sequences, and decorators</li>
<li>Add conditions and action parameters</li>
<li>Model relationships with perspectives</li>
<li>Define time-based schedules</li>
<li>Track character development with life arcs</li>
</ul>
<h2 id="where-to-go-next"><a class="header" href="#where-to-go-next">Where to Go Next</a></h2>
<ul>
<li><strong><a href="../reference/09-overview.html">Reference Guide</a></strong>: Complete syntax specifications</li>
<li><strong><a href="../advanced/20-patterns.html">Design Patterns</a></strong>: Common patterns and best practices</li>
<li><strong><a href="../examples/24-baker-family-complete.html">Examples Gallery</a></strong>: Full working examples to learn from</li>
</ul>
<hr />
<p><strong>Reference</strong>: For complete life arc syntax, see the <a href="../reference/13-life-arcs.html">Life Arcs Reference</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/07-schedules.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../tutorial/09-locations-institutions.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../tutorial/07-schedules.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../tutorial/09-locations-institutions.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>