release: Storybook v0.2.0 - Major syntax and features update
BREAKING CHANGES: - Relationship syntax now requires blocks for all participants - Removed self/other perspective blocks from relationships - Replaced 'guard' keyword with 'if' for behavior tree decorators Language Features: - Add tree-sitter grammar with improved if/condition disambiguation - Add comprehensive tutorial and reference documentation - Add SBIR v0.2.0 binary format specification - Add resource linking system for behaviors and schedules - Add year-long schedule patterns (day, season, recurrence) - Add behavior tree enhancements (named nodes, decorators) Documentation: - Complete tutorial series (9 chapters) with baker family examples - Complete reference documentation for all language features - SBIR v0.2.0 specification with binary format details - Added locations and institutions documentation Examples: - Convert all examples to baker family scenario - Add comprehensive working examples Tooling: - Zed extension with LSP integration - Tree-sitter grammar for syntax highlighting - Build scripts and development tools Version Updates: - Main package: 0.1.0 → 0.2.0 - Tree-sitter grammar: 0.1.0 → 0.2.0 - Zed extension: 0.1.0 → 0.2.0 - Storybook editor: 0.1.0 → 0.2.0
This commit is contained in:
845
docs/book/reference/13-life-arcs.html
Normal file
845
docs/book/reference/13-life-arcs.html
Normal file
@@ -0,0 +1,845 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Life Arcs - Storybook Language Guide</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="icon" href="../favicon.svg">
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Storybook Language Guide</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="life-arcs"><a class="header" href="#life-arcs">Life Arcs</a></h1>
|
||||
<p>Life arcs are state machines that model how characters or other entities evolve over time. They define discrete states and the conditions under which an entity transitions between states. Life arcs capture character development, quest progress, relationship phases, and any other finite-state processes.</p>
|
||||
<h2 id="what-is-a-life-arc"><a class="header" href="#what-is-a-life-arc">What is a Life Arc?</a></h2>
|
||||
<p>A life arc is a finite state machine (FSM) composed of:</p>
|
||||
<ul>
|
||||
<li><strong>States</strong>: Discrete phases or modes (e.g., “happy”, “angry”, “sleeping”)</li>
|
||||
<li><strong>Transitions</strong>: Conditional edges between states (e.g., “when health < 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"><a class="header" href="#syntax">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"><a class="header" href="#states">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"><a class="header" href="#on-enter-actions">On-Enter Actions</a></h2>
|
||||
<p>When entering a state, the <code>on enter</code> block updates entity fields.</p>
|
||||
<h3 id="syntax-1"><a class="header" href="#syntax-1">Syntax</a></h3>
|
||||
<pre><code class="language-storybook">state state_name {
|
||||
on enter {
|
||||
EntityName.field_name: value
|
||||
EntityName.other_field: other_value
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="examples"><a class="header" href="#examples">Examples</a></h3>
|
||||
<pre><code class="language-storybook">life_arc CharacterMood {
|
||||
state happy {
|
||||
on enter {
|
||||
Martha.emotional_state: "happy"
|
||||
Martha.energy: 100
|
||||
}
|
||||
}
|
||||
|
||||
state tired {
|
||||
on enter {
|
||||
Martha.emotional_state: "exhausted"
|
||||
Martha.energy: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="field-update-semantics"><a class="header" href="#field-update-semantics">Field Update Semantics</a></h3>
|
||||
<ul>
|
||||
<li><strong>Target</strong>: <code>EntityName.field_name</code> must reference an existing character/entity</li>
|
||||
<li><strong>Value</strong>: Any valid <a href="./18-value-types.html">value type</a></li>
|
||||
<li><strong>Effect</strong>: Field is set to the specified value when state is entered</li>
|
||||
<li><strong>Timing</strong>: Happens immediately upon transition to the state</li>
|
||||
</ul>
|
||||
<h3 id="multiple-field-updates"><a class="header" href="#multiple-field-updates">Multiple Field Updates</a></h3>
|
||||
<pre><code class="language-storybook">state exhausted_baker {
|
||||
on enter {
|
||||
Martha.energy: 0.1
|
||||
Martha.mood: "stressed"
|
||||
Martha.quality_output: 0.7
|
||||
Martha.needs_break: true
|
||||
}
|
||||
|
||||
---narrative
|
||||
After a sixteen-hour shift during the harvest festival,
|
||||
Martha's hands are shaking. She knows her bread quality is
|
||||
suffering and reluctantly steps away from the oven.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="transitions"><a class="header" href="#transitions">Transitions</a></h2>
|
||||
<p>Transitions define conditional edges between states. When a transition’s condition becomes true, the state machine moves to the target state.</p>
|
||||
<h3 id="syntax-2"><a class="header" href="#syntax-2">Syntax</a></h3>
|
||||
<pre><code class="language-storybook">state source_state {
|
||||
on condition_expression -> 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="./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"><a class="header" href="#complete-examples">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"><a class="header" href="#execution-semantics">Execution Semantics</a></h2>
|
||||
<h3 id="tick-based-evaluation"><a class="header" href="#tick-based-evaluation">Tick-Based Evaluation</a></h3>
|
||||
<p>Life arcs evaluate transitions every “tick” (typically once per frame):</p>
|
||||
<ol>
|
||||
<li><strong>Current state</strong>: Start in the current state</li>
|
||||
<li><strong>Evaluate transitions</strong>: Check each outgoing 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"><a class="header" href="#transition-priority">Transition Priority</a></h3>
|
||||
<p>When multiple transitions could fire, the <strong>first one in declaration order</strong> is taken:</p>
|
||||
<pre><code class="language-storybook">state combat {
|
||||
on health < 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"><a class="header" href="#validation-rules">Validation Rules</a></h2>
|
||||
<ol>
|
||||
<li><strong>At least one state</strong>: Life arc must contain at least one state</li>
|
||||
<li><strong>Unique state names</strong>: State names must be unique within the life arc</li>
|
||||
<li><strong>Valid transitions</strong>: Transition targets must reference defined states</li>
|
||||
<li><strong>No orphan states</strong>: All states should be reachable (warning, not error)</li>
|
||||
<li><strong>Expression validity</strong>: Transition conditions must be well-formed expressions</li>
|
||||
<li><strong>Field targets</strong>: On-enter field updates must reference valid entities/fields</li>
|
||||
<li><strong>No cyclic immediate transitions</strong>: Avoid transitions that fire immediately in a loop</li>
|
||||
</ol>
|
||||
<h2 id="design-patterns"><a class="header" href="#design-patterns">Design Patterns</a></h2>
|
||||
<h3 id="1-hub-and-spoke"><a class="header" href="#1-hub-and-spoke">1. Hub-and-Spoke</a></h3>
|
||||
<p>A central “hub” state with transitions to specialized states:</p>
|
||||
<pre><code class="language-storybook">life_arc HubPattern {
|
||||
state idle {
|
||||
on combat_triggered -> 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"><a class="header" href="#best-practices">Best Practices</a></h2>
|
||||
<h3 id="1-use-descriptive-state-names"><a class="header" href="#1-use-descriptive-state-names">1. Use Descriptive State Names</a></h3>
|
||||
<p><strong>Avoid:</strong></p>
|
||||
<pre><code class="language-storybook">state s1 { ... }
|
||||
state s2 { ... }
|
||||
</code></pre>
|
||||
<p><strong>Prefer:</strong></p>
|
||||
<pre><code class="language-storybook">state waiting_for_player { ... }
|
||||
state engaged_in_combat { ... }
|
||||
</code></pre>
|
||||
<h3 id="2-add-narrative-prose-blocks"><a class="header" href="#2-add-narrative-prose-blocks">2. Add Narrative Prose Blocks</a></h3>
|
||||
<pre><code class="language-storybook">state master_baker {
|
||||
---narrative
|
||||
Master Baker Elena. She has earned it. The guild acknowledges
|
||||
her mastery, and Martha beams with pride. Elena begins
|
||||
mentoring her own apprentice.
|
||||
---
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="3-order-transitions-by-priority"><a class="header" href="#3-order-transitions-by-priority">3. Order Transitions by Priority</a></h3>
|
||||
<pre><code class="language-storybook">state health_check {
|
||||
on health <= 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"><a class="header" href="#cross-references">Cross-References</a></h2>
|
||||
<ul>
|
||||
<li><a href="./10-characters.html">Characters</a> - Characters can have associated life arcs</li>
|
||||
<li><a href="./17-expressions.html">Expression Language</a> - Transition condition syntax</li>
|
||||
<li><a href="./18-value-types.html">Value Types</a> - On-enter field value types</li>
|
||||
<li><a href="./19-validation.html">Validation Rules</a> - Life arc validation constraints</li>
|
||||
</ul>
|
||||
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
|
||||
<ul>
|
||||
<li><strong>Finite State Machines (FSM)</strong>: Life arcs are FSMs</li>
|
||||
<li><strong>Character development</strong>: Track character growth over time</li>
|
||||
<li><strong>Quest states</strong>: Model quest progression</li>
|
||||
<li><strong>Mood systems</strong>: Model emotional states</li>
|
||||
<li><strong>Lifecycle modeling</strong>: Birth, growth, aging, death</li>
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../reference/12-decorators.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../reference/14-schedules.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../reference/12-decorators.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../reference/14-schedules.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user