Files
storybook/docs/book/tutorial/02-creating-characters.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

478 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>Creating Characters - Storybook Language Guide</title>
<!-- Custom HTML head -->
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Storybook Language Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="creating-characters"><a class="header" href="#creating-characters">Creating Characters</a></h1>
<p>Characters are the heart of every Storybook world. In this chapter, you will learn how to define characters with fields, prose blocks, species, and templates.</p>
<h2 id="a-simple-character"><a class="header" href="#a-simple-character">A Simple Character</a></h2>
<p>The simplest character has a name and some fields:</p>
<pre><code class="language-storybook">character Martha {
age: 34
skill_level: 0.95
is_open: true
}
</code></pre>
<p>Fields use the <code>name: value</code> format. Storybook supports several value types:</p>
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Example</th><th>Description</th></tr></thead><tbody>
<tr><td>Integer</td><td><code>42</code></td><td>Whole numbers</td></tr>
<tr><td>Float</td><td><code>0.85</code></td><td>Decimal numbers</td></tr>
<tr><td>String</td><td><code>"hello"</code></td><td>Text in double quotes</td></tr>
<tr><td>Boolean</td><td><code>true</code> / <code>false</code></td><td>Yes or no values</td></tr>
<tr><td>Time</td><td><code>14:30</code></td><td>Clock times</td></tr>
<tr><td>Duration</td><td><code>2h30m</code></td><td>Time intervals</td></tr>
<tr><td>List</td><td><code>[1, 2, 3]</code></td><td>Ordered collections</td></tr>
</tbody></table>
</div>
<h2 id="adding-descriptions-with-prose-blocks"><a class="header" href="#adding-descriptions-with-prose-blocks">Adding Descriptions with Prose Blocks</a></h2>
<p>Prose blocks embed narrative text directly alongside data. They start and end with <code>---</code> and have a tag name:</p>
<pre><code class="language-storybook">character Martha {
age: 34
skill_level: 0.95
---backstory
Martha learned to bake from her grandmother, starting at age
twelve with simple bread recipes. She mastered sourdough and
pastries, eventually opening the most popular bakery in town.
---
---appearance
A confident woman in her mid-thirties, usually dusted with
flour. Her hands are calloused from years of kneading dough.
---
}
</code></pre>
<p>You can use any tag name you like. Common ones include <code>backstory</code>, <code>appearance</code>, <code>personality</code>, <code>motivation</code>, and <code>secrets</code>.</p>
<h2 id="defining-species"><a class="header" href="#defining-species">Defining Species</a></h2>
<p>Species define what a character fundamentally <em>is</em>. Define them separately, then reference them with the <code>:</code> syntax:</p>
<pre><code class="language-storybook">species Human {
lifespan: 70
---description
Bipedal mammals with complex language and tool use.
---
}
species Cat {
lifespan: 15
---description
Domestic cats make loyal companions and effective
pest control for bakeries.
---
}
</code></pre>
<p>Now use species when creating characters:</p>
<pre><code class="language-storybook">character Martha: Human {
age: 34
}
character Whiskers: Cat {
friendly: true
catches_mice: true
}
</code></pre>
<p>The <code>: Human</code> part says “Martha is a Human.” She inherits the species fields (like <code>lifespan: 70</code>) automatically.</p>
<p>A character can have only one species you cannot be both Human and Cat.</p>
<h3 id="but-what-about-hybrids"><a class="header" href="#but-what-about-hybrids">But what about hybrids?</a></h3>
<p>If you want a character that combines traits from different sources, use <strong>composition with templates</strong> instead:</p>
<pre><code class="language-storybook">species Human {
lifespan: 70
reasoning_ability: 1.0
}
template CulinaryExpert {
palate_sensitivity: 0.5..1.0
recipes_mastered: 0..500
can_identify_ingredients: true
}
template BusinessOwner {
business_acumen: 0.0..1.0
manages_finances: true
leadership: 0.0..1.0
}
// A character combining species with multiple templates
character Martha: Human from CulinaryExpert, BusinessOwner {
age: 34
// From CulinaryExpert
palate_sensitivity: 0.9
recipes_mastered: 150
can_identify_ingredients: true
// From BusinessOwner
business_acumen: 0.8
manages_finances: true
leadership: 0.85
// Unique traits
specialty: "sourdough"
years_experience: 22
---personality
A perfectionist in the kitchen who demands the best from her
ingredients and her team. Warm with customers but exacting
with her apprentices.
---
}
</code></pre>
<p>By combining a species with templates, you can achieve any combination you need. The species defines <em>what</em> the character fundamentally is, while templates add <em>traits</em> from other sources.</p>
<h2 id="reusing-traits-with-templates"><a class="header" href="#reusing-traits-with-templates">Reusing Traits with Templates</a></h2>
<p>Templates define reusable sets of attributes. Characters inherit from them using the <code>from</code> keyword:</p>
<pre><code class="language-storybook">template SkilledWorker {
skill_level: 0.0..1.0
years_experience: 0..50
}
template Baker {
include SkilledWorker
specialty: "general"
recipes_mastered: 0..500
}
</code></pre>
<p>Notice the <code>0.0..1.0</code> syntax that is a <strong>range</strong>. When a character uses this template, a specific value within that range is selected. Ranges are only valid in templates.</p>
<p>Characters can inherit from multiple templates:</p>
<pre><code class="language-storybook">character Martha: Human from Baker, BusinessOwner {
age: 34
skill_level: 0.95
specialty: "sourdough"
recipes_mastered: 150
}
</code></pre>
<p>The <code>from Baker, BusinessOwner</code> part says “Martha has the traits from both templates.” You can override any inherited field by specifying it directly.</p>
<h2 id="species-vs-templates"><a class="header" href="#species-vs-templates">Species vs. Templates</a></h2>
<p>Understanding the difference is important:</p>
<div class="table-wrapper"><table><thead><tr><th></th><th>Species (<code>:</code>)</th><th>Templates (<code>from</code>)</th></tr></thead><tbody>
<tr><td><strong>Meaning</strong></td><td>What the character <em>is</em></td><td>What the character <em>has</em></td></tr>
<tr><td><strong>Count</strong></td><td>Exactly one</td><td>Zero or more</td></tr>
<tr><td><strong>Example</strong></td><td><code>: Human</code></td><td><code>from Baker, BusinessOwner</code></td></tr>
</tbody></table>
</div>
<p>Think of it this way: a character <strong>is</strong> a Human, but <strong>has</strong> baking skills and business acumen.</p>
<h2 id="field-resolution"><a class="header" href="#field-resolution">Field Resolution</a></h2>
<p>When a character inherits from multiple sources, fields are resolved in priority order:</p>
<ol>
<li><strong>Species fields</strong> (lowest priority)</li>
<li><strong>Template fields</strong> (left to right in the <code>from</code> clause)</li>
<li><strong>Character fields</strong> (highest priority always wins)</li>
</ol>
<pre><code class="language-storybook">species Human {
speed: 1.0
}
template Warrior {
speed: 1.5
strength: 10
}
template Berserker {
speed: 2.0
strength: 15
}
character Conan: Human from Warrior, Berserker {
strength: 20
}
// Resolved: speed = 2.0 (Berserker), strength = 20 (Conan)
</code></pre>
<h2 id="using-enums-for-controlled-values"><a class="header" href="#using-enums-for-controlled-values">Using Enums for Controlled Values</a></h2>
<p>Enums define a fixed set of named values. They prevent typos and ensure consistency:</p>
<pre><code class="language-storybook">enum SkillLevel {
Novice,
Beginner,
Intermediate,
Advanced,
Expert,
Master
}
enum Specialty {
Sourdough,
Pastries,
Cakes,
Bread,
Confections
}
</code></pre>
<p>Use enum values as field values:</p>
<pre><code class="language-storybook">character Martha: Human {
skill_level: Master
specialty: Sourdough
}
</code></pre>
<p>If you write <code>skill_level: Professional</code>, the compiler will catch the mistake because <code>Professional</code> is not defined in the <code>SkillLevel</code> enum.</p>
<h2 id="importing-across-files"><a class="header" href="#importing-across-files">Importing Across Files</a></h2>
<p>Real projects split definitions across multiple files. Use the <code>use</code> statement to import:</p>
<pre><code class="language-storybook">// In world/characters/martha.sb
use schema::core_enums::{SkillLevel, Specialty};
use schema::templates::Baker;
use schema::beings::Human;
character Martha: Human from Baker {
skill_level: Master
specialty: Sourdough
}
</code></pre>
<p>The <code>use schema::core_enums::{SkillLevel, Specialty}</code> line imports two enums from the <code>schema/core_enums.sb</code> file. You can also import everything with <code>use schema::core_enums::*</code>.</p>
<h2 id="putting-it-all-together"><a class="header" href="#putting-it-all-together">Putting It All Together</a></h2>
<p>Here is a complete character definition with all the features:</p>
<pre><code class="language-storybook">use schema::core_enums::{SkillLevel, Specialty};
use schema::templates::{Baker, BusinessOwner};
use schema::beings::Human;
character Martha: Human from Baker, BusinessOwner {
// Core identity
age: 34
skill_level: Master
specialty: Sourdough
// Professional
years_experience: 22
recipes_mastered: 150
can_teach: true
// Business
business_acumen: 0.8
leadership: 0.85
---backstory
Martha learned to bake from her grandmother, starting at age
twelve with simple bread recipes. She mastered sourdough and
pastries, eventually opening the most popular bakery in town.
---
---personality
A perfectionist in the kitchen who demands the best from her
ingredients and her team. Warm with customers but exacting
with her apprentices. Known for arriving at 4 AM to start
the morning batch.
---
}
</code></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<p>Now that Martha exists, let us give her something to do. In <a href="./03-first-behavior-tree.html">Your First Behavior Tree</a>, you will learn how characters make decisions.</p>
<hr />
<p><strong>Reference</strong>: For complete character syntax details, see the <a href="../reference/10-characters.html">Characters Reference</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../tutorial/01-welcome.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../tutorial/03-first-behavior-tree.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../tutorial/01-welcome.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../tutorial/03-first-behavior-tree.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>