Storybook Language Guide

Create rich narrative simulations through code that reads like stories.

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.


⚠️ Alpha Software Notice

Storybook is currently in alpha and under active development at r3t Studios. While the core language features are stable and ready to use, you should expect:

  • New features to be added as we expand the language capabilities
  • Minor syntax adjustments as we refine the design based on real-world usage
  • API changes in the compiled output format as we optimize for game engine integration

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!


What is Storybook?

Storybook is a compiled simulation language designed for open-world, autonomous game simulations. 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.

Storybook defines characters, behaviors, relationships, and narrative events for autonomous agents in dynamic worlds. It bridges the gap between storytelling and technical simulation through:

  • Readable syntax - Code that looks like natural descriptions, but compiles to efficient bytecode
  • Named nodes - Behavior trees you can read as stories, that drive AI decision-making
  • Prose blocks - Embed narrative directly in definitions for context-aware storytelling
  • Rich semantics - From simple traits to complex state machines and schedules
  • Game engine integration - Designed to power autonomous NPCs in Unity, Unreal, Godot, and custom engines

Choose Your Path

🎨 For Storytellers

New to programming? Start with the Tutorial Track for a gentle, example-driven introduction. Learn by building a bakery simulation!

💻 For Developers

Need technical precision? Jump to the Reference Guide for complete syntax specifications and semantic details.

✨ For Everyone

Want inspiration? Browse the Examples Gallery to see what’s possible!

Quick Start

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 { ... }
    }
}

Continue to Tutorial →

Documentation Structure

  • Part I: Getting Started - Tutorials for learning Storybook
  • Part II: Complete Reference - Technical specifications
  • Part III: Advanced Topics - Patterns and integration
  • Part IV: Examples Gallery - Complete working examples

Getting Help

  • In-Editor: Hover over keywords for quick help
  • Search: Use the search box (top right) to find anything
  • Examples: Working code is the best teacher!

Ready to begin? Start with the Tutorial →

Welcome to Storybook

Bring characters to life with code that reads like stories.

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.

What You Will Learn

In this tutorial, we follow Martha and her bakery family, using their daily lives to learn each concept:

  1. Creating Characters - Define Martha with traits and descriptions
  2. Your First Behavior Tree - Give characters decision-making abilities
  3. Making Characters Act - Actions, conditions, and decorators
  4. Advanced Behaviors - Subtrees, parameters, and complex patterns
  5. Character Relationships - Model how characters interact
  6. Schedules and Time - Give characters daily routines
  7. Life Arcs - Track character development over time

What is Storybook?

Storybook is a domain-specific language (DSL) for narrative simulation. It lets you describe:

  • Who characters are (traits, backstory, species)
  • What they do (behavior trees with decision logic)
  • How they relate to others (relationships with perspectives)
  • When they act (schedules and time-based routines)
  • How they change (life arcs and state machines)

All of this in syntax designed to be readable and expressive.

Your First Storybook File

Create a file called hello.sb and add this:

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.
    ---
}

That is it. You have defined a character with two numeric fields and a prose description block. Let us break it down:

  • character Martha declares a new character named Martha
  • { ... } contains her attributes
  • age: 34 is an integer field
  • skill_level: 0.95 is a floating-point field (0.0 to 1.0)
  • ---description ... --- is a prose block for narrative text

Key Concepts

Everything is a Declaration

Storybook files contain declarations – named definitions of things in your world:

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

Fields Hold Data

Fields use a simple name: value format:

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

Prose Blocks Tell Stories

Prose blocks embed narrative text directly in your definitions:

---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.
---

You can have multiple prose blocks with different tags (backstory, appearance, personality, etc.) in a single declaration.

Project Structure

A typical Storybook project organizes files into directories:

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

Files reference each other using use statements:

use schema::core_enums::SkillLevel;
use schema::beings::Human;

character Martha: Human {
    skill_level: expert
}

Next Steps

Ready to create your first character? Head to Creating Characters to start building Martha in detail.


Tip: You do not need to memorize everything now. This tutorial builds concepts gradually, and you can always refer back to the Reference Guide for precise syntax details.

Creating Characters

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.

A Simple Character

The simplest character has a name and some fields:

character Martha {
    age: 34
    skill_level: 0.95
    is_open: true
}

Fields use the name: value format. Storybook supports several value types:

TypeExampleDescription
Integer42Whole numbers
Float0.85Decimal numbers
String"hello"Text in double quotes
Booleantrue / falseYes or no values
Time14:30Clock times
Duration2h30mTime intervals
List[1, 2, 3]Ordered collections

Adding Descriptions with Prose Blocks

Prose blocks embed narrative text directly alongside data. They start and end with --- and have a tag name:

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.
    ---
}

You can use any tag name you like. Common ones include backstory, appearance, personality, motivation, and secrets.

Defining Species

Species define what a character fundamentally is. Define them separately, then reference them with the : syntax:

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.
    ---
}

Now use species when creating characters:

character Martha: Human {
    age: 34
}

character Whiskers: Cat {
    friendly: true
    catches_mice: true
}

The : Human part says “Martha is a Human.” She inherits the species’ fields (like lifespan: 70) automatically.

A character can have only one species – you cannot be both Human and Cat.

But what about hybrids?

If you want a character that combines traits from different sources, use composition with templates instead:

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.
    ---
}

By combining a species with templates, you can achieve any combination you need. The species defines what the character fundamentally is, while templates add traits from other sources.

Reusing Traits with Templates

Templates define reusable sets of attributes. Characters inherit from them using the from keyword:

template SkilledWorker {
    skill_level: 0.0..1.0
    years_experience: 0..50
}

template Baker {
    include SkilledWorker
    specialty: "general"
    recipes_mastered: 0..500
}

Notice the 0.0..1.0 syntax – that is a range. When a character uses this template, a specific value within that range is selected. Ranges are only valid in templates.

Characters can inherit from multiple templates:

character Martha: Human from Baker, BusinessOwner {
    age: 34
    skill_level: 0.95
    specialty: "sourdough"
    recipes_mastered: 150
}

The from Baker, BusinessOwner part says “Martha has the traits from both templates.” You can override any inherited field by specifying it directly.

Species vs. Templates

Understanding the difference is important:

Species (:)Templates (from)
MeaningWhat the character isWhat the character has
CountExactly oneZero or more
Example: Humanfrom Baker, BusinessOwner

Think of it this way: a character is a Human, but has baking skills and business acumen.

Field Resolution

When a character inherits from multiple sources, fields are resolved in priority order:

  1. Species fields (lowest priority)
  2. Template fields (left to right in the from clause)
  3. Character fields (highest priority – always wins)
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)

Using Enums for Controlled Values

Enums define a fixed set of named values. They prevent typos and ensure consistency:

enum SkillLevel {
    Novice,
    Beginner,
    Intermediate,
    Advanced,
    Expert,
    Master
}

enum Specialty {
    Sourdough,
    Pastries,
    Cakes,
    Bread,
    Confections
}

Use enum values as field values:

character Martha: Human {
    skill_level: Master
    specialty: Sourdough
}

If you write skill_level: Professional, the compiler will catch the mistake because Professional is not defined in the SkillLevel enum.

Importing Across Files

Real projects split definitions across multiple files. Use the use statement to import:

// 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
}

The use schema::core_enums::{SkillLevel, Specialty} line imports two enums from the schema/core_enums.sb file. You can also import everything with use schema::core_enums::*.

Putting It All Together

Here is a complete character definition with all the features:

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.
    ---
}

Next Steps

Now that Martha exists, let us give her something to do. In Your First Behavior Tree, you will learn how characters make decisions.


Reference: For complete character syntax details, see the Characters Reference.

Your First Behavior Tree

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.

What is a Behavior Tree?

A behavior tree is a hierarchy of nodes that executes from top to bottom. Each node either succeeds or fails, and the tree uses that result to decide what to do next.

There are two fundamental building blocks:

  • choose (Selector): Try children in order until one succeeds. Think “try A, else try B, else try C.”
  • then (Sequence): Run children in order, stopping if any fails. Think “do A, then B, then C – all must succeed.”

Your First Tree

Let us give Martha a simple baking behavior:

behavior Martha_BakeRoutine {
    choose what_to_do {
        then fill_special_orders {
            CheckSpecialOrders
            PrepareSpecialIngredients
            BakeSpecialItem
        }

        then daily_bread {
            MixDough
            KneadDough
            BakeLoaves
        }

        CleanWorkstation
    }
}

Reading this as a story:

Martha will choose what to do. First, she tries to fill special orders: she checks for orders, prepares special ingredients, and bakes the item. If that path fails (maybe there are no special orders), she tries daily bread: she mixes dough, kneads it, and bakes loaves. If even that fails, she simply cleans her workstation.

Understanding choose (Selector)

A choose 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.

choose response {
    HandleUrgentOrder   // Try first: handle urgent order
    ServeCustomer       // If that fails: serve a customer
    RestockShelves      // If that fails: restock
}

This is like a priority list – the first successful option wins.

Understanding then (Sequence)

A then 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.

then make_sourdough {
    MixDough         // Must succeed
    KneadDough       // Must succeed
    FirstRise        // Must succeed
    ShapeLoaves      // Must succeed
}

If MixDough fails (no flour available), the whole process stops.

Naming Your Nodes

Both choose and then accept optional labels:

choose daily_priority {
    then morning_baking { ... }
    then afternoon_sales { ... }
}

Labels are optional but highly recommended. They make your trees readable as narratives and help with debugging. Compare:

// 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 }
}

Combining choose and then

Behavior trees become powerful when you nest selectors and sequences:

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
    }
}

Reading this as narrative:

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.

Actions

The leaf nodes in a behavior tree are actions – concrete things a character does:

MixDough              // Simple action
KneadDough            // Simple action
ServeCustomer         // Simple action

Actions are identifiers that the runtime interprets. They represent the actual behaviors executed in your simulation.

A Complete Example

Here is a behavior tree for the morning rush at the bakery:

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
        }
    }
}

Notice the prose block (---description ... ---) at the top of the behavior. You can document what the behavior does right alongside the code.

Behavior-Character Connection

Characters link to behaviors using the uses behaviors clause:

character Martha: Human {
    age: 34

    uses behaviors: [
        { tree: Martha_BakeRoutine },
        { tree: HandleEmergency }
    ]
}

This tells the simulation that Martha uses two behavior trees. We will cover advanced behavior linking (priorities, conditions) in Making Characters Act.

Next Steps

Your behavior trees so far make decisions between options and run sequences of actions. In Making Characters Act, you will learn how to add conditions, decorators, and parameters to create truly dynamic behaviors.


Reference: For complete behavior tree syntax, see the Behavior Trees Reference.

Making Characters Act

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.

Conditions: if and when

Conditions let behavior trees react to the world. Use if or when to test a condition before proceeding:

behavior Martha_React {
    choose response {
        then bake_path {
            if(inventory_sufficient)
            StartBaking
        }

        then restock_path {
            if(inventory_low)
            OrderSupplies
        }

        CleanWorkstation
    }
}

if(inventory_sufficient) succeeds when inventory is sufficient, and fails otherwise. If it fails, the entire bake_path sequence fails, and the tree moves on to the next option.

if and when are interchangeable – use whichever reads more naturally:

// "if" for state checks
if(health < 20)

// "when" for event-like conditions
when(alarm_triggered)

Condition Expressions

Conditions support comparisons and logical operators:

// 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)

Action Parameters

Actions can take named parameters using parenthesis syntax:

behavior Martha_BakeSpecial {
    then baking {
        MixDough(recipe: "sourdough", quantity: 10)
        KneadDough(duration: 15m)
        BakeLoaves(temperature: 230, duration: 35m)
    }
}

Parameters are fields inside ( ) after the action name. They let you customize behavior without defining separate actions for each variation.

Decorators

Decorators wrap a single child node and modify its behavior. They are your tools for timing, repetition, and conditional execution.

repeat – Looping

// 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
}

invert – Flip Results

Inverts success/failure. Useful for “if NOT” conditions:

behavior SafeBake {
    choose options {
        then bake_safely {
            invert { OvenOverheating }   // Succeeds if oven is NOT overheating
            ContinueBaking
        }

        StopAndInspect
    }
}

retry – Try Again on Failure

Retries the child up to N times if it fails:

retry(3) {
    LightOven    // Try up to 3 times before giving up
}

timeout – Time Limits

Fails the child if it does not complete within the duration:

timeout(10s) {
    WaitForDoughToRise    // Must finish within 10 seconds
}

cooldown – Rate Limiting

Prevents the child from running again within the cooldown period:

cooldown(30s) {
    CheckOvenTemperature    // Can only check once every 30 seconds
}

if as Decorator (Guard)

The if decorator only runs the child when a condition is true:

if(has_special_orders) {
    PrepareSpecialBatch    // Only prepare when there are orders
}

This is different from if as a condition node. As a decorator, if wraps a child and gates its execution. As a condition node, if is a simple pass/fail check inline in a sequence.

succeed_always and fail_always

Force a result regardless of the child:

// Try bonus task, but don't fail the routine if it fails
succeed_always {
    ExperimentWithNewRecipe
}

// Temporarily disable a feature
fail_always {
    UntestedBakingMethod
}

Combining Decorators

Decorators can nest for complex control:

behavior ResilientAction {
    // Only run if oven is ready, with 20s timeout, retrying up to 3 times
    if(oven_ready) {
        timeout(20s) {
            retry(3) {
                BakeDelicateItem
            }
        }
    }
}

Execution flows outside-in: first the if checks the oven, then the timeout starts, then the retry begins.

Subtree References

The include keyword references another behavior tree, enabling reuse:

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
    }
}

Subtrees help you avoid duplicating behavior logic. You can also reference behaviors from other modules using qualified paths:

include behaviors::baking::sourdough
include behaviors::service::greet_customer

Behavior Linking with Priorities

Characters can link to multiple behaviors with priorities and conditions:

character Martha: Human {
    uses behaviors: [
        {
            tree: BakerRoutine
            priority: normal
        },
        {
            tree: HandleEmergency
            when: emergency_detected
            priority: high
        },
        {
            tree: HandleHealthInspection
            when: inspector_present
            priority: critical
        }
    ]
}

The runtime evaluates behaviors by priority (critical > high > normal > low). Higher-priority behaviors preempt lower-priority ones when their conditions are met.

A Complete Example

Here is a complete behavior tree for handling the morning rush:

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
        }
    }
}

This tree repeats forever: serve a customer or restock the display, check the ovens, and prepare the next batch.

Next Steps

You now know the full toolkit for behavior trees. In Advanced Behaviors, you will learn patterns for building complex AI systems with nested trees, state-based switching, and modular design.


Reference: See Decorators Reference for all decorator types and Expression Language for complete condition syntax.

Advanced Behaviors

You have learned the fundamentals of behavior trees. This chapter covers advanced patterns: complex decision hierarchies, modular design with subtrees, and state-driven behavior.

Deep Decision Trees

Real characters need layered decision-making. Nest selectors and sequences to create rich AI:

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
            }
        }
    }
}

Each level of nesting refines the decision. The outer choose selects the time of day; inner nodes handle the specifics.

Modular Subtrees

Large behavior trees become unwieldy. Break them into focused subtrees and compose with include:

// 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
    }
}

Benefits of modular subtrees:

  • Each subtree is testable in isolation
  • Multiple characters can share subtrees
  • Changes propagate automatically

Conditional Behavior Selection

Use conditions to switch between behavioral modes:

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
        }
    }
}

Decorator Combinations

Combine decorators to build sophisticated control patterns:

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
                    }
                }
            }
        }
    }
}

Prose Documentation

Add narrative context to complex behaviors with prose blocks:

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
            }
        }
    }
}

Design Tips

Prefer shallow trees: Break deep nesting into subtrees with include.

Name every composite node: Labels make trees self-documenting.

Use decorators for control flow: Timing, repetition, and gating belong in decorators, not in action logic.

Keep actions atomic: Each action should do one thing. Complex operations are sequences of simple actions.

Next Steps

Characters do not exist in isolation. In Character Relationships, you will model how characters connect to each other – friendships, rivalries, parent-child bonds, and more.


Reference: See Behavior Trees Reference and Decorators Reference for complete syntax.

Character Relationships

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.

Basic Relationships

The simplest relationship connects two characters with shared fields:

relationship MarthaAndEmma {
    Martha as parent {}
    Emma as child {}

    bond: 0.95
    type: "parent_child"
}

This says Martha and Emma share a relationship with a bond strength of 0.95 (very close). The bond field is shared – it applies equally to both participants.

Adding Roles

Roles label each participant’s function in the relationship:

relationship ParentChild {
    Martha as parent
    Emma as child

    bond: 0.95
    guardianship: true
}

The as parent and as child labels clarify who plays which role. Roles are descriptive – you can use any name that makes sense.

Perspectives: Self and Other

Real relationships are not symmetric. How one person sees the relationship may differ from how the other sees it. Storybook handles this with self and other blocks:

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
}

Reading this:

  • Martha’s self view: She feels patient (80%), highly invested in her student
  • Martha’s view of Elena (other): Sees high potential (85%) with low frustration (20%)
  • Elena’s self view: Dedicated (90%) but sometimes overwhelmed (40%)
  • Elena’s view of Martha (other): Deep admiration (95%), strong desire to impress (90%)
  • Shared: Their bond strength is 0.85

Prose in Relationships

Relationships can include narrative descriptions for each participant:

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
}

Multi-Party Relationships

Relationships can involve more than two participants:

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.
    ---
}

Asymmetric Awareness

Relationships can model situations where one party does not know the relationship exists:

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
}

Institutional Relationships

Institutions can participate in relationships too:

relationship GuildMembership {
    Martha as member
    BakersGuild as organization

    membership_since: "2015-01-01"
    standing: "good"
    dues_paid: true
}

Building a Relationship Web

Multiple relationships create a rich social network:

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
}

Next Steps

Characters have traits, behaviors, and relationships. In Schedules and Time, you will give them daily routines and time-based activities.


Reference: For complete relationship syntax, see the Relationships Reference.

Schedules and Time

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.

Basic Schedules

A schedule contains time blocks, each with a time range and an action:

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
    }
}

Time ranges use 24-hour clock format (HH:MM). The action field links to a behavior tree that drives the character’s activity during that block.

Named Blocks

Blocks can have names (like morning_prep above). Named blocks are important for schedule composition – they allow child schedules to override specific blocks by name.

Linking Schedules to Characters

Characters use the uses schedule clause:

character Baker: Human {
    uses schedule: SimpleBaker
}

For multiple schedules:

character Innkeeper: Human {
    uses schedules: [WeekdaySchedule, WeekendSchedule]
}

Temporal Constraints

Blocks can be restricted to specific times using temporal constraints:

Season Constraints

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
    }
}

Day of Week Constraints

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
    }
}

Temporal constraint values (like summer or monday) reference enums defined in your storybook:

enum Season { spring, summer, fall, winter }
enum DayOfWeek { monday, tuesday, wednesday, thursday, friday, saturday, sunday }

Recurring Events

Use recurs to define events that repeat on a schedule:

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
        }
    }
}

Recurrences take priority over regular blocks. On Saturdays, the MarketDay blocks replace the regular work block.

Schedule Composition with extends

Schedules can extend other schedules, inheriting and overriding blocks:

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
    }
}

The EarlyBaker schedule overrides the open block by name – same block name, different hours. Any blocks not overridden are inherited unchanged.

You can chain extensions:

schedule MasterBaker extends EarlyBaker {
    block open {
        03:00 - 11:00
        action: baking::master_work
    }

    block teaching {
        14:00 - 16:00
        action: baking::teach_apprentice
    }
}

MasterBaker overrides open again and adds a new teaching block.

Overnight Blocks

Time ranges can span midnight:

schedule NightGuard {
    block night_patrol {
        22:00 - 06:00
        action: security::patrol
    }
}

The system interprets this as 22:00 to midnight on day one, then midnight to 06:00 on day two.

A Complete Schedule Example

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
        }
    }
}

Next Steps

Characters now have traits, behaviors, relationships, and schedules. In Life Arcs, you will learn how to model character development over time – how they grow, change, and evolve through different phases of life.


Reference: For complete schedule syntax, see the Schedules Reference.

Life Arcs

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.

What is a Life Arc?

A life arc defines:

  • States: Distinct phases (e.g., “apprentice”, “journeyman”, “master”)
  • Transitions: Conditions that move between states (e.g., “when skill > 80, become master”)
  • On-enter actions: Changes that happen when entering a state (e.g., set confidence to 0.9)

Your First Life Arc

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
        }
    }
}

Reading this as a story:

The baker starts as an apprentice with low skill and confidence. When skill exceeds 0.5, they become a journeyman with improved confidence. When skill exceeds 0.8, they become a master who is confident and can teach others.

States

Each state represents a distinct phase. States contain:

  • on enter block: Field updates that happen when entering the state
  • Transitions: Conditions that trigger moving to another state
  • Prose blocks: Narrative descriptions
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.
    ---
}

Transitions

Transitions use expressions to decide when to change state:

// 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

Transition Priority

When multiple transitions could fire, the first one in declaration order wins:

state combat {
    on health < 10 -> desperate    // Checked first
    on health < 50 -> defensive    // Checked second
    on surrounded -> retreat       // Checked third
}

If health is 5, only desperate triggers, even though defensive is also true.

On-Enter Actions

The on enter block sets field values when entering a state. It runs exactly once per transition:

state master_baker {
    on enter {
        Elena.skill_level: master
        Elena.confidence: commanding
        Elena.can_teach: true
    }
}

The syntax is EntityName.field: value. This updates the referenced character’s field.

Elena’s Complete Journey

Here is a complete life arc tracking Elena’s growth from apprentice to master:

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.
        ---
    }
}

Common Patterns

Hub-and-Spoke

A central idle state with branches to specialized states:

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 }
}

Linear Progression

States form a one-way sequence (quests, tutorials):

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 {}
}

Cyclic States

States form a loop (day/night, seasons, mood swings):

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 }
}

Tips

Order transitions by urgency: Put the most critical conditions first, since the first true transition wins.

Use descriptive state names: waiting_for_customer is clearer than state1.

Add narrative prose blocks: They make life arcs readable as stories and serve as documentation.

Avoid orphan states: Every state should be reachable from some other state (the compiler will warn you about unreachable states).

What You Have Learned

Congratulations! You have completed the tutorial. You now know how to:

  • Create characters with species, templates, and enums
  • Build behavior trees with selectors, sequences, and decorators
  • Add conditions and action parameters
  • Model relationships with perspectives
  • Define time-based schedules
  • Track character development with life arcs

Where to Go Next


Reference: For complete life arc syntax, see the Life Arcs Reference.

Locations and Institutions

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 be and organizations to belong to. That is what locations and institutions provide.


What Are Locations?

A location 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.

Your First Location

Let’s create the bakery where Martha works:

location BakersBakery {
    type: bakery
    address: "14 Main Street"
    capacity: 30
}

That is all it takes. The location 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.

Adding Detail

A real location needs more than three fields. Let’s flesh out the bakery:

location BakersBakery {
    type: bakery
    address: "14 Main Street"
    capacity: 30
    employees: 4
    specialty: "artisan sourdough"
    daily_output_loaves: 80..120
    open: true
    established: "2011"
}

Notice daily_output_loaves: 80..120 – that is a range. Each simulation run can pick a different number of loaves, adding natural variation.

Prose Blocks

Bare fields are good for data, but locations also need narrative flavor. Use prose blocks:

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.
    ---
}

Prose blocks start with ---tag_name and end with ---. The tag name (description here) becomes the key. You can have as many prose blocks as you want:

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...
    ---
}

Building a World with Locations

Locations work best when they form a coherent world. Here is the Baker family’s neighborhood:

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.
    ---
}

Modeling Hierarchy

Storybook does not enforce a built-in parent-child relationship for locations. Instead, you use fields to express hierarchy:

location MainStreet {
    type: street
    district: TownCenter
    shops: 12
}

location BakersBakery {
    type: bakery
    street: MainStreet
    district: TownCenter
}

This convention-based approach keeps the language simple while letting you model whatever spatial relationships your world needs.


What Are Institutions?

An institution 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 use behaviors and schedules, just like characters.

Your First Institution

institution BakersGuild {
    type: trade_guild
    members: 50
    founded: "1892"
    reputation: 0.85
}

This looks just like a location so far. The difference comes when you add behaviors.

Institutions with Behaviors

Institutions can act. The uses behaviors clause links behavior trees to the institution:

institution BakersGuild {
    type: trade_guild
    members: 50
    reputation: 0.85

    uses behaviors: [
        { tree: ManageApprentices },
        { tree: NegotiateSuppliers },
        { tree: HostEvents }
    ]
}

Each entry in the list is a behavior link object with a tree field. This tells the simulation engine that the Bakers Guild can manage apprentices, negotiate with suppliers, and host events.

Behavior Priorities

Not all behaviors are equally important. Use the priority field:

institution BakersGuild {
    type: trade_guild

    uses behaviors: [
        { tree: ManageApprentices, priority: normal },
        { tree: NegotiateSuppliers, priority: high },
        { tree: HostEvents, priority: low }
    ]
}

Priority levels are low, normal, high, and critical. Higher-priority behaviors take precedence when the institution must choose between actions.

Conditional Behaviors

Some behaviors only activate under certain conditions:

institution BakersGuild {
    type: trade_guild
    reputation: 0.85

    uses behaviors: [
        { tree: ManageApprentices },
        { tree: NegotiateSuppliers, priority: high },
        { tree: EmergencyMeeting, when: reputation < 0.3, priority: critical }
    ]
}

The when clause uses an expression. Here, the emergency meeting behavior only activates when reputation drops below 0.3.

Institutions with Schedules

Institutions can also follow schedules:

institution BakersGuild {
    type: trade_guild
    uses schedule: GuildOperatingHours
}

For multiple schedules:

institution BakersGuild {
    type: trade_guild
    uses schedules: [WeekdaySchedule, WeekendSchedule]
}

Prose Blocks

Just like locations, institutions support prose blocks:

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.
    ---
}

Connecting Characters to Institutions

Institutions do not have a built-in membership list. You model membership through character fields or relationships.

Through Character Fields

The simplest approach – add fields to your characters:

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"
}

Through Relationships

For richer modeling, use relationships:

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
}

This approach captures richer information: roles, duration, and multi-party connections.


Locations vs. Institutions

When should you use each?

QuestionUse…
Where does something happen?Location
Who or what organizes things?Institution
Does it need behaviors?Institution
Does it need a schedule?Institution
Is it purely a place?Location
Is it a group or organization?Institution

Sometimes the same concept needs both:

// 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
}

The guild hall is a place. The guild is an organization. Keeping them separate lets you say “the guild meets at the guild hall” without conflating the building with the institution.


Putting It All Together

Here is a complete example showing how locations, institutions, and characters work together in the Baker family world:

// 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
}

Key Takeaways

  1. Locations are simple: name, fields, prose blocks. They model places.
  2. Institutions are richer: they add uses behaviors and uses schedule on top of fields and prose. They model organizations.
  3. Membership is modeled through character fields or relationships, not built into institution syntax.
  4. Separate place from organization: A guild hall (location) and the guild (institution) are different things.
  5. Use enums for type-safe categorization of locations and institutions.

Next Steps

Language Overview

The Storybook language enables narrative simulation through structured declarations of characters, behaviors, relationships, and events.

Philosophy

Storybook is a domain-specific language for narrative simulation, influenced by:

  • Rust: Strong typing, explicit declarations, and clear ownership semantics
  • C#: Object-oriented patterns with declarative syntax
  • Python: Readable, accessible syntax that prioritizes clarity

The language balances technical precision with narrative expressiveness, making it accessible to storytellers while maintaining the rigor developers need.

Design Principles

1. Code as Narrative

Named nodes and prose blocks let code read like stories:

behavior Baker_MorningRoutine {
    choose daily_priority {
        then prepare_sourdough { ... }
        then serve_customers { ... }
        then restock_display { ... }
    }
}

2. Explicit is Better Than Implicit

Every declaration is self-documenting:

  • Character fields show what defines them
  • Behavior trees show decision structures
  • Relationships name their participants

3. Progressive Disclosure

Simple cases are simple, complex cases are possible:

  • Basic characters need just a name and fields
  • Templates enable inheritance and reuse
  • Advanced features (state machines, decorators) available when needed

4. Semantic Validation

The compiler catches narrative errors:

  • Bond values must be 0.0..1.0
  • Schedule blocks can’t overlap
  • Life arc transitions must reference valid states

Language Structure

Declaration Types

Storybook has 10 top-level declaration types:

DeclarationPurposeExample
characterDefine entities with traits and behaviorsA baker with skills and schedule
templateReusable patterns with rangesA generic NPC template
behaviorDecision trees for actionsHow a character responds to events
life_arcState machines for life stagesApprentice → Baker → Master
scheduleTime-based activitiesDaily routine from 6am to 10pm
relationshipConnections between entitiesParent-child with bond values
institutionOrganizations and groupsA bakery with employees
locationPlaces with propertiesThe town square
speciesType definitions with traitsHuman vs Cat vs Rabbit
enumNamed value setsEmotionalState options

Value Types

Fields can contain:

  • Primitives: 42, 3.14, "text", true
  • Time: 08:30:00, 14:15
  • Duration: 2h30m, 45s
  • Ranges: 20..40 (for templates)
  • Identifiers: OtherCharacter, path::to::Thing
  • Lists: [1, 2, 3]
  • Objects: { field: value }
  • Prose blocks: ---tag\nMulti-line\ntext\n---

Expression Language

Conditions and queries use:

  • Comparisons: age > 18, energy <= 0.5
  • Equality: status is active, ready is true
  • Logic: tired and hungry, rich or lucky, not ready
  • Field access: self.health, other.bond
  • Quantifiers: forall x in children: x.happy

Compilation Model

Source → AST → SBIR → Runtime

.sb files → Parser → Abstract Syntax Tree → Resolver → SBIR Binary

SBIR (Storybook Intermediate Representation) is a compact binary format that:

  • Resolves all cross-file references
  • Validates semantic constraints
  • Optimizes for simulation runtime

Validation Layers

  1. Lexical: Valid tokens and syntax
  2. Syntactic: Correct grammar structure
  3. Semantic: Type checking, reference resolution
  4. Domain: Narrative constraints (bond ranges, schedule overlaps)

File Organization

Project Structure

my-storybook/
├── characters/
│   ├── baker.sb
│   └── family.sb
├── behaviors/
│   └── daily_routine.sb
├── world/
│   ├── locations.sb
│   └── institutions.sb
└── schema/
    ├── species.sb
    └── templates.sb

Import System

Use use statements to reference definitions from other files:

use schema::species::Human;
use schema::templates::Adult;

character Baker: Human from Adult {
    // ...
}

Resolution order:

  1. Same file
  2. Explicitly imported
  3. Error if not found

Quick Reference

Character Declaration

character Name: Species from Template {
    field: value
    field: value
    ---prose_tag
    Text content
    ---
}

Behavior Tree

behavior Name {
    choose label {           // Selector
        then label { ... }   // Sequence
        if (condition)       // Condition
        ActionName          // Action
        include path        // Subtree
    }
}

Life Arc

life_arc Name {
    state StateName {
        on condition -> NextState
    }
}

Schedule

schedule Name {
    08:00 -> 12:00: activity { }
    12:00 -> 13:00: lunch { }
}

Relationship

relationship Name {
    Person1 as role
    Person2 as role
    bond: 0.85
}

Next Steps

Dive deeper into each declaration type:


Philosophy Note: Storybook treats narrative as data. Characters aren’t objects with methods - they’re declarations of traits, connected by behaviors and relationships. This separation enables rich analysis, modification, and simulation of narrative worlds.

Characters

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.

Syntax

<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> "---"

Components

Name

The character’s identifier. Must be unique within its scope and follow standard identifier rules (alphanumeric + underscore, cannot start with digit).

Species (Optional)

The species clause (: SpeciesName) defines what the character fundamentally is. This is distinct from templates, which define what attributes they have.

  • Purpose: Ontological typing—what kind of being this is
  • Validation: Must reference a defined species declaration
  • Single inheritance: A character can only have one species
  • Default behavior: Species fields are inherited automatically

Example:

character Martha: Human {
    age: 34
}

Template Inheritance (Optional)

The template clause (from Template1, Template2) specifies templates from which the character inherits fields. Templates provide reusable attribute sets.

  • Purpose: Compositional inheritance—mix-and-match capabilities and traits
  • Multiple inheritance: Characters can inherit from multiple templates
  • Merge semantics: Fields from later templates override earlier ones
  • Override allowed: Character fields override all inherited fields

Example:

character Martha: Human from Baker, BusinessOwner {
    specialty: "sourdough"
}

Fields

Fields define the character’s attributes using the standard field syntax. All value types are supported.

Common field categories:

  • Physical traits: height, weight, age, eye_color
  • Personality: confidence, patience, dedication
  • Professional: skill_level, specialty, recipes_mastered
  • State tracking: energy, mood, orders_today
  • Capabilities: can_teach, can_work_independently

Prose Blocks

Characters can contain multiple prose blocks for narrative content. Common tags:

  • ---backstory: Character history and origin
  • ---appearance: Physical description
  • ---personality: Behavioral traits and quirks
  • ---motivation: Goals and desires
  • ---secrets: Hidden information

Prose blocks are narrative-only and do not affect simulation logic.

Behavior Integration

Characters can link to behavior trees using the uses behaviors clause.

character Guard {
    uses behaviors: [
        {
            tree: combat::patrol_route
            priority: normal
        },
        {
            tree: combat::engage_intruder
            when: threat_detected
            priority: high
        }
    ]
}

Each behavior link includes:

  • tree: Qualified path to the behavior tree (required)
  • when: Condition expression for activation (optional)
  • priority: Execution priority (optional, default: normal)

See Behavior Trees for details on behavior tree syntax and semantics.

Schedule Integration

Characters can follow schedules using the uses schedule or uses schedules clause.

character Baker {
    uses schedule: BakerySchedule
}

character Innkeeper {
    uses schedules: [WeekdaySchedule, WeekendSchedule]
}
  • Single schedule: uses schedule: ScheduleName
  • Multiple schedules: uses schedules: [Schedule1, Schedule2]

The runtime selects the appropriate schedule based on temporal constraints. See Schedules for composition and selection semantics.

Species vs. Templates

The distinction between species (:) and templates (from) reflects ontological vs. compositional typing:

FeatureSpecies (:)Templates (from)
SemanticsWhat the character isWhat the character has
CardinalityExactly oneZero or more
Example: Human, : Dragonfrom Warrior, Mage
PurposeFundamental natureReusable trait sets
OverrideCan override species fieldsCan override template fields

Example showing both:

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
}

Field Resolution Order

When a character inherits from species and templates, fields are resolved in this order (later overrides earlier):

  1. Species fields (base ontology)
  2. Template fields (left to right in from clause)
  3. Character fields (highest priority)

Example:

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)

Validation Rules

The Storybook compiler enforces these validation rules:

  1. Unique names: Character names must be unique within their module
  2. Species exists: If specified, the species must reference a defined species declaration
  3. Templates exist: All templates in the from clause must reference defined template declarations
  4. No circular inheritance: Templates cannot form circular dependency chains
  5. Field type consistency: Field values must match expected types from species/templates
  6. Reserved fields: Cannot use reserved keywords as field names
  7. Behavior trees exist: All behavior tree references must resolve to defined behavior declarations
  8. Schedules exist: All schedule references must resolve to defined schedule declarations
  9. Prose tag uniqueness: Each prose tag can appear at most once per character

Examples

Basic Character

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.
    ---
}

Character with Species

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.
    ---
}

Character with Template Inheritance

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.
    ---
}

Character with All Features

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.
    ---
}

Character with Overrides

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"
}

Use Cases

Protagonist Definition

Define rich, dynamic protagonists with complex attributes:

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.
    ---
}

NPC Templates

Create diverse NPCs from templates:

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
}

Ensemble Casts

Define multiple related characters:

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.
    ---
}

Cross-References

  • Instantiation: Characters are concrete instances; they cannot be instantiated further
  • Composition: Prefer template composition over deep species hierarchies
  • Modularity: Characters can reference behaviors and schedules from other modules
  • Narrative-driven: Use prose blocks to embed storytelling directly with data

Behavior Trees

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.

What is a Behavior Tree?

A behavior tree is a hierarchical structure that executes from root to leaves, making decisions based on success/failure of child nodes:

  • Composite nodes (choose, then) have multiple children and control flow
  • Condition nodes (if, when) test predicates
  • Action nodes execute concrete behaviors
  • Decorator nodes (repeat, invert, timeout, etc.) modify child behavior
  • Subtree nodes (include) reference other behavior trees

Behavior trees are evaluated every tick (frame), flowing through the tree from root to leaves until a node returns success or failure.

Syntax

<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>

Composite Nodes

Selector: choose

A selector tries its children in order until one succeeds. Returns success if any child succeeds, failure if all fail.

Execution:

  1. Try first child
  2. If succeeds -> return success
  3. If fails -> try next child
  4. If all fail -> return failure

Use case: “Try A, if that fails try B, if that fails try C…”

behavior GuardBehavior {
    choose guard_actions {
        AttackIntruder      // Try first
        SoundAlarm          // If attack fails, sound alarm
        FleeInPanic         // If alarm fails, flee
    }
}

Named selectors:

choose service_options {
    then serve_regular {
        CustomerIsWaiting
        TakeOrder
    }

    then restock_display {
        DisplayIsLow
        FetchFromKitchen
    }
}

Labels are optional but recommended for complex trees–they improve readability and debugging.

Sequence: then

A sequence runs its children in order until one fails. Returns success only if all children succeed, failure if any fails.

Execution:

  1. Run first child
  2. If fails -> return failure
  3. If succeeds -> run next child
  4. If all succeed -> return success

Use case: “Do A, then B, then C… all must succeed”

behavior BrewingSequence {
    then brew_potion {
        GatherIngredients   // Must succeed
        MixIngredients      // Then this must succeed
        Boil                // Then this must succeed
        BottlePotion        // Finally this
    }
}

Named sequences:

then prepare_sourdough {
    MixDough
    KneadDough
    ShapeLoaves
}

Nesting Composites

Composite nodes can nest arbitrarily deep:

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
    }
}

Condition Nodes

Conditions test expressions and return success/failure based on the result.

if vs. when

Both are semantically identical–use whichever reads better in context:

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
        }
    }
}

Condition Syntax

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

See Expression Language for complete expression syntax.

Action Nodes

Actions are leaf nodes that execute concrete behaviors. They reference action implementations in the runtime.

Basic Actions

behavior SimpleActions {
    then do_things {
        MoveForward
        TurnLeft
        Attack
    }
}

Actions with Parameters

Actions can have named parameters using parenthesis syntax:

behavior ParameterizedActions {
    then patrol {
        MoveTo(destination: "Waypoint1", speed: 1.5)
        WaitFor(duration: 5s)
        MoveTo(destination: "Waypoint2", speed: 1.5)
    }
}

Parameter passing:

  • Parameters are passed as fields (name: value)
  • All value types supported
  • Runtime validates parameter types
behavior RichParameters {
    then complex_action {
        CastSpell(spell: "Fireball", target: enemy_position, power: 75, multicast: false)
        Heal(amount: 50, target: self)
    }
}

Decorator Nodes

Decorators wrap a single child node and modify its behavior. See Decorators for complete reference.

Common Decorators

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
        }
    }
}

Subtree References

The include keyword references another behavior tree, enabling modularity and reuse.

Basic Subtree

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
    }
}

Qualified Subtree References

behavior ComplexAI {
    choose ai_modes {
        include behaviors::combat::melee
        include behaviors::combat::ranged
        include behaviors::exploration::search
        include behaviors::social::greet
    }
}

Named Nodes

Both composite nodes (choose, then) can have optional labels:

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
            }
        }
    }
}

Benefits:

  • Readability: Tree structure is self-documenting
  • Debugging: Named nodes appear in execution traces
  • Narrative: Labels can be narrative (“handle_special_orders” vs. anonymous node)

Guidelines:

  • Use labels for important structural nodes
  • Use descriptive, narrative names
  • Omit labels for trivial nodes

Complete Examples

Simple Guard AI

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
        }
    }
}

Complex NPC Behavior

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
            }
        }
    }
}

Morning Baking Routine

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
        }
    }
}

Repeating Behavior

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
        }
    }
}

Retry Logic for Delicate Recipes

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
            }
        }
    }
}

Integration with Characters

Characters link to behaviors using the uses behaviors clause:

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
        }
    ]
}

Behavior selection:

  • Multiple behaviors evaluated by priority (critical > high > normal > low)
  • Conditions (when clause) gate behavior activation
  • Higher-priority behaviors preempt lower-priority ones

See Characters for complete behavior linking syntax.

Execution Semantics

Tick-Based Evaluation

Behavior trees execute every “tick” (typically once per frame):

  1. Start at root node
  2. Traverse down to leaves based on composite logic
  3. Execute leaf nodes (conditions, actions)
  4. Return success/failure up the tree
  5. Repeat next tick

Node Return Values

Every node returns one of:

  • Success: Node completed successfully
  • Failure: Node failed
  • Running: Node still executing (async action)

Stateful vs. Stateless

  • Stateless nodes: Evaluate fresh every tick (conditions, simple actions)
  • Stateful nodes: Maintain state across ticks (decorators, long actions)

Example of stateful behavior:

behavior LongAction {
    timeout(30s) {           // Stateful: tracks elapsed time
        ComplexCalculation   // May take multiple ticks
    }
}

Validation Rules

  1. At least one node: Behavior body must contain at least one node
  2. Composite children: choose and then require at least one child
  3. Decorator child: Decorators require exactly one child
  4. Action exists: Action names must reference registered actions in runtime
  5. Subtree exists: include must reference a defined behavior declaration
  6. Expression validity: Condition expressions must be well-formed
  7. Duration format: Decorator durations must be valid (e.g., 5s, 10m)
  8. Unique labels: Node labels (if used) should be unique within their parent
  9. Parameter types: Action parameters must match expected types

Best Practices

1. Prefer Shallow Trees

Avoid:

choose {
    then { then { then { then { Action } } } }  // Too deep!
}

Prefer:

choose root {
    include combat_tree
    include exploration_tree
    include social_tree
}

2. Use Named Nodes for Clarity

Avoid:

choose {
    then {
        IsHungry
        FindFood
        Eat
    }
    Wander
}

Prefer:

choose survival {
    then eat_if_hungry {
        IsHungry
        FindFood
        Eat
    }
    Wander
}

3. Subtrees for Reusability

Avoid: Duplicating logic across behaviors

Prefer:

behavior Combat_Common {
    then attack_sequence {
        DrawWeapon
        Approach
        Strike
    }
}

behavior Warrior {
    include Combat_Common
}

behavior Guard {
    include Combat_Common
}

4. Decorators for Timing

Avoid: Manual timing in actions

Prefer:

timeout(10s) {
    ComplexTask
}

cooldown(30s) {
    SpecialAbility
}

5. Guard for Preconditions

Avoid:

then problematic {
    ExpensiveAction  // Always runs even if inappropriate
}

Prefer:

if(can_afford_action) {
    ExpensiveAction  // Only runs when condition passes
}

Cross-References

  • Reactive AI: Behavior trees continuously react to changing conditions
  • Hierarchical decision-making: Composite nodes create decision hierarchies
  • Modularity: Subtrees enable behavior reuse and composition
  • Narrative-driven design: Named nodes make behavior trees readable as stories

Decorators

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.

What Are Decorators?

Decorators sit between a parent and child node, transforming the child’s behavior:

Parent
  └─ Decorator
       └─ Child

The decorator intercepts the child’s execution, potentially:

  • Repeating it multiple times
  • Timing it out
  • Inverting its result
  • Guarding its execution
  • Forcing a specific result

Decorator Types

Storybook provides 10 decorator types:

DecoratorPurposeExample
repeatLoop infinitelyrepeat { Patrol }
repeat(N)Loop N timesrepeat(3) { Check }
repeat(min..max)Loop min to max timesrepeat(2..5) { Search }
invertFlip success/failureinvert { IsEnemy }
retry(N)Retry on failure (max N times)retry(5) { Open }
timeout(duration)Fail after durationtimeout(10s) { Solve }
cooldown(duration)Run at most once per durationcooldown(30s) { Fire }
if(condition)Only run if condition trueif(health > 50) { Attack }
succeed_alwaysAlways return successsucceed_always { Try }
fail_alwaysAlways return failurefail_always { Test }

Syntax

<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")

Repeat Decorators

Infinite Repeat: repeat

Loops the child infinitely. The child is re-executed immediately after completing (success or failure).

behavior InfinitePatrol {
    repeat {
        PatrolRoute
    }
}

Execution:

  1. Run child
  2. Child completes (success or failure)
  3. Immediately run child again
  4. Go to step 2 (forever)

Use cases:

  • Perpetual patrols
  • Endless background processes
  • Continuous monitoring

Warning: Infinite loops never return to parent. Ensure they’re appropriate for your use case.

Repeat N Times: repeat N

Repeats the child exactly N times, then returns success.

behavior CheckThreeTimes {
    repeat(3) {
        CheckDoor
    }
}

Execution:

  1. Counter = 0
  2. Run child
  3. Counter++
  4. If counter < N, go to step 2
  5. Return success

Use cases:

  • Fixed iteration counts
  • “Try three times then give up”
  • Deterministic looping

Repeat Range: repeat min..max

Repeats the child between min and max times. At runtime, a specific count is selected within the range.

behavior SearchRandomly {
    repeat(2..5) {
        SearchArea
    }
}

Execution:

  1. Select count C randomly from [min, max]
  2. Repeat child C times (as in repeat N)

Use cases:

  • Variable behavior
  • Procedural variation
  • Non-deterministic actions

Validation:

  • min ≥ 0
  • max ≥ min
  • Both must be integers

Invert Decorator

Inverts the child’s return value: success becomes failure, failure becomes success.

behavior AvoidEnemies {
    invert {
        IsEnemyNearby  // Success if NOT nearby
    }
}

Truth table:

Child returnsDecorator returns
SuccessFailure
FailureSuccess
RunningRunning (unchanged)

Use cases:

  • Negating conditions (“if NOT X”)
  • Inverting success criteria
  • Converting “found” to “not found”

Example:

behavior SafeExploration {
    choose safe_actions {
        // Only explore if NOT dangerous
        then explore {
            invert { IsDangerous }
            ExploreArea
        }

        // Default: Stay put
        Wait
    }
}

Retry Decorator

Retries the child up to N times on failure. Returns success if any attempt succeeds, failure if all N attempts fail.

behavior PersistentDoor {
    retry(5) {
        OpenLockedDoor
    }
}

Execution:

  1. Attempts = 0
  2. Run child
  3. If child succeeds → return success
  4. If child fails:
    • Attempts++
    • If attempts < N, go to step 2
    • Else return failure

Use cases:

  • Unreliable actions (lockpicking, persuasion)
  • Network/resource operations
  • Probabilistic success

Example with context:

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
    }
}

Validation:

  • N must be ≥ 1

Timeout Decorator

Fails the child if it doesn’t complete within the specified duration.

behavior TimeLimitedPuzzle {
    timeout(30s) {
        SolvePuzzle
    }
}

Execution:

  1. Start timer
  2. Run child each tick
  3. If child completes before timeout → return child’s result
  4. If timer expires → return failure (interrupt child)

Use cases:

  • Time-limited actions
  • Preventing infinite loops
  • Enforcing deadlines

Duration formats:

  • 5s - 5 seconds
  • 10m - 10 minutes
  • 2h - 2 hours
  • 3d - 3 days

Example:

behavior QuickDecision {
    choose timed_choice {
        // Give AI 5 seconds to find optimal move
        timeout(5s) {
            CalculateOptimalStrategy
        }

        // Fallback: Use simple heuristic
        UseQuickHeuristic
    }
}

Notes:

  • Timer starts when decorator is first entered
  • Timer resets if decorator exits and re-enters
  • Child node should handle interruption gracefully

Cooldown Decorator

Prevents the child from running more than once per cooldown period. Fails immediately if called within cooldown.

behavior SpecialAbility {
    cooldown(30s) {
        FireCannon
    }
}

Execution:

  1. Check last execution time
  2. If (current_time - last_time) < cooldown → return failure
  3. Else:
    • Run child
    • Record current_time as last_time
    • Return child’s result

Use cases:

  • Rate-limiting abilities
  • Resource cooldowns (spells, items)
  • Preventing spam

Example:

behavior Mage_SpellCasting {
    choose spells {
        // Fireball: 10 second cooldown
        cooldown(10s) {
            CastFireball
        }

        // Lightning: 5 second cooldown
        cooldown(5s) {
            CastLightning
        }

        // Basic attack: No cooldown
        MeleeAttack
    }
}

State management:

  • Cooldown state persists across behavior tree ticks
  • Each cooldown decorator instance has independent state
  • Cooldown timers are per-entity (not global)

If Decorator

Only runs the child if the condition is true. Fails immediately if condition is false.

behavior ConditionalAttack {
    if(health > 50) {
        AggressiveAttack
    }
}

Execution:

  1. Evaluate condition expression
  2. If true → run child and return its result
  3. If false → return failure (do not run child)

Use cases:

  • Preconditions (“only if X”)
  • Resource checks (“only if have mana”)
  • Safety checks (“only if safe”)

Expression syntax: See Expression Language for complete syntax.

Examples:

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
        }
    }
}

Comparison with bare if conditions:

// 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
    }
}

The if decorator with a body is more efficient for gating expensive subtrees.

Force Result Decorators

succeed_always

Always returns success, regardless of child’s actual result.

behavior TryOptionalTask {
    succeed_always {
        AttemptBonus  // Even if fails, we don't care
    }
}

Use cases:

  • Optional tasks that shouldn’t block progress
  • Logging/monitoring actions
  • Best-effort operations

Example:

behavior QuestSequence {
    then main_quest {
        TalkToNPC

        // Try to find secret, but don't fail quest if not found
        succeed_always {
            SearchForSecretDoor
        }

        ReturnToQuestGiver
    }
}

fail_always

Always returns failure, regardless of child’s actual result.

behavior TestFailure {
    fail_always {
        AlwaysSucceedsAction  // But we force it to fail
    }
}

Use cases:

  • Testing/debugging
  • Forcing alternative paths
  • Disabling branches temporarily

Example:

behavior UnderConstruction {
    choose abilities {
        // Temporarily disabled feature
        fail_always {
            NewExperimentalAbility
        }

        // Fallback to old ability
        ClassicAbility
    }
}

Combining Decorators

Decorators can nest to create complex behaviors:

behavior ComplexPattern {
    // Repeat 3 times, each with 10 second timeout
    repeat(3) {
        timeout(10s) {
            SolveSubproblem
        }
    }
}

More complex nesting:

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
                }
            }
        }
    }
}

Execution order: Outside → Inside

  1. If checks condition
  2. Timeout starts timer
  3. Retry begins first attempt
  4. Cooldown checks last execution time
  5. Child action runs

Duration Syntax

Timeout and cooldown decorators use duration literals:

<duration-literal> ::= <number> <unit>

<unit> ::= "s"    // seconds
         | "m"    // minutes
         | "h"    // hours
         | "d"    // days

<number> ::= <digit>+

Examples:

  • 5s - 5 seconds
  • 30s - 30 seconds
  • 2m - 2 minutes
  • 10m - 10 minutes
  • 1h - 1 hour
  • 24h - 24 hours
  • 7d - 7 days

Validation:

  • Number must be positive integer
  • No compound durations (use 120s not 2m if runtime expects seconds)
  • No fractional units (1.5m not allowed; use 90s)

Comparison Table

DecoratorAffects SuccessAffects FailureAffects RunningStateful
repeatRepeatRepeatWaitYes (counter)
repeat NRepeatRepeatWaitYes (counter)
repeat min..maxRepeatRepeatWaitYes (counter)
invert→ Failure→ SuccessUnchangedNo
retry N→ SuccessRetry or failWaitYes (attempts)
timeout dur→ Success→ Success→ Failure if expiredYes (timer)
cooldown dur→ Success→ Success→ SuccessYes (last time)
if(expr)→ Success→ Success→ SuccessNo
succeed_always→ Success→ Success→ SuccessNo
fail_always→ Failure→ Failure→ FailureNo

Stateful decorators maintain state across ticks. Stateless decorators evaluate fresh every tick.

Validation Rules

  1. Child required: All decorators must have exactly one child node
  2. Repeat count: repeat N requires N ≥ 0
  3. Repeat range: repeat min..max requires 0 ≤ min ≤ max
  4. Retry count: retry N requires N ≥ 1
  5. Duration positive: Timeout/cooldown durations must be > 0
  6. Duration format: Must match <number><unit> (e.g., 10s, 5m)
  7. Guard expression: Guard condition must be valid expression
  8. No empty decorators: repeat { } is invalid (missing child)

Use Cases by Category

Timing Control

  • timeout: Prevent infinite loops, enforce time limits
  • cooldown: Rate-limit abilities, prevent spam
  • repeat: Continuous processes, patrols

Reliability

  • retry: Handle unreliable actions, probabilistic success
  • if: Precondition checks, resource validation
  • succeed_always: Optional tasks, best-effort

Logic Control

  • invert: Negate conditions, flip results
  • fail_always: Disable branches, testing

Iteration

  • repeat N: Fixed loops, deterministic behavior
  • repeat min..max: Variable loops, procedural variation

Best Practices

1. Use Guards for Expensive Checks

Avoid:

then expensive {
    if(complex_condition)
    ExpensiveOperation
}

Prefer:

if(complex_condition) {
    ExpensiveOperation  // Only runs if condition passes
}

2. Combine Timeout with Retry

Avoid: Infinite retry loops

Prefer:

timeout(30s) {
    retry(5) {
        UnreliableAction
    }
}

3. Use Cooldowns for Rate Limiting

Avoid: Manual timing in actions

Prefer:

cooldown(10s) {
    FireCannon
}

4. Invert for Readable Conditions

Avoid:

choose options {
    then branch_a {
        if(not is_dangerous)
        Explore
    }
}

Prefer:

choose options {
    then branch_a {
        invert { IsDangerous }
        Explore
    }
}

5. succeed_always for Optional Tasks

Avoid:

then quest {
    MainTask
    choose optional {
        BonusTask
        DoNothing  // Awkward fallback
    }
    NextTask
}

Prefer:

then quest {
    MainTask
    succeed_always { BonusTask }  // Try bonus, don't fail quest
    NextTask
}

Cross-References

  • Composability: Decorators can nest for complex control flow
  • Separation of concerns: Decorators handle control flow, children handle logic
  • State management: Stateful decorators (repeat, retry, timeout, cooldown) persist across ticks
  • Performance: Guards prevent unnecessary child execution

Life Arcs

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.

What is a Life Arc?

A life arc is a finite state machine (FSM) composed of:

  • States: Discrete phases or modes (e.g., “happy”, “angry”, “sleeping”)
  • Transitions: Conditional edges between states (e.g., “when health < 20, go to ‘fleeing’”)
  • On-enter actions: Field updates that occur when entering a state
[State A] --condition--> [State B] --condition--> [State C]
    |                         |                       |
 on enter                  on enter                on enter
 (set fields)              (set fields)            (set fields)

Life arcs run continuously, evaluating transitions every tick and moving between states as conditions become true.

Syntax

<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> "---"

States

A state represents a discrete phase in an entity’s lifecycle.

Basic State

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.
        ---
    }
}

State Names

State names are identifiers that must be unique within the life arc. Use descriptive names:

  • Good: idle, combat, sleeping, enlightened
  • Avoid: state1, s, temp

On-Enter Actions

When entering a state, the on enter block updates entity fields.

Syntax

state state_name {
    on enter {
        EntityName.field_name: value
        EntityName.other_field: other_value
    }
}

Examples

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
        }
    }
}

Field Update Semantics

  • Target: EntityName.field_name must reference an existing character/entity
  • Value: Any valid value type
  • Effect: Field is set to the specified value when state is entered
  • Timing: Happens immediately upon transition to the state

Multiple Field Updates

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.
    ---
}

Transitions

Transitions define conditional edges between states. When a transition’s condition becomes true, the state machine moves to the target state.

Syntax

state source_state {
    on condition_expression -> target_state
    on another_condition -> another_target
}

Basic Transitions

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 {}
}

Expression Conditions

Transitions use the full expression language:

Comparisons:

on health < 20 -> low_health
on distance > 100 -> far_away
on count == 0 -> empty

Boolean fields:

on is_hostile -> combat
on completed -> done
on not ready -> waiting

Logical operators:

on health < 20 and not has_potion -> desperate
on is_day or is_lit -> visible

Complex conditions:

on (health < 50 and enemy_count > 3) or surrounded -> retreat
on forall e in enemies: e.defeated -> victory

Multiple Transitions

A state can have multiple outgoing transitions:

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
}

Evaluation order:

  • Transitions are evaluated in declaration order (top to bottom)
  • First transition with a true condition is taken
  • If no conditions are true, state remains unchanged

Self-Transitions

A state can transition to itself:

state patrolling {
    on enemy_spotted -> combat
    on checkpoint_reached -> patrolling  // Reset patrol state
}

Complete Examples

Elena’s Career Journey

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.
        ---
    }
}

Quest Progress

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
    }
}

Name Checker (Equality Examples)

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
    }
}

Status Monitor

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
    }
}

Character Mood Swings

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
    }
}

Execution Semantics

Tick-Based Evaluation

Life arcs evaluate transitions every “tick” (typically once per frame):

  1. Current state: Start in the current state
  2. Evaluate transitions: Check each outgoing transition’s condition (in order)
  3. First true transition: Take the first transition with a true condition
  4. Enter new state: Execute the new state’s on enter block
  5. Repeat next tick

Transition Priority

When multiple transitions could fire, the first one in declaration order is taken:

state combat {
    on health < 10 -> desperate       // Checked first
    on health < 50 -> defensive       // Checked second
    on surrounded -> retreat          // Checked third
}

If health is 5, only desperate transition fires (even though defensive condition is also true).

State Persistence

The current state persists across ticks until a transition fires.

state waiting {
    on signal_received -> active
}

The entity remains in waiting state indefinitely until signal_received becomes true.

On-Enter Execution

on enter blocks execute once when entering the state, not every tick:

state combat {
    on enter {
        Character.weapon_drawn: true  // Runs once when entering combat
    }
}

Validation Rules

  1. At least one state: Life arc must contain at least one state
  2. Unique state names: State names must be unique within the life arc
  3. Valid transitions: Transition targets must reference defined states
  4. No orphan states: All states should be reachable (warning, not error)
  5. Expression validity: Transition conditions must be well-formed expressions
  6. Field targets: On-enter field updates must reference valid entities/fields
  7. No cyclic immediate transitions: Avoid transitions that fire immediately in a loop

Design Patterns

1. Hub-and-Spoke

A central “hub” state with transitions to specialized states:

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
    }
}

2. Linear Progression

States form a linear sequence (quests, tutorials):

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 {}
}

3. Cyclic States

States form a cycle (day/night, seasons):

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
    }
}

4. Hierarchical (Simulated)

Use multiple life arcs for hierarchical state:

// 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
    }
}

Best Practices

1. Use Descriptive State Names

Avoid:

state s1 { ... }
state s2 { ... }

Prefer:

state waiting_for_player { ... }
state engaged_in_combat { ... }

2. Add Narrative Prose Blocks

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.
    ---
}

3. Order Transitions by Priority

state health_check {
    on health <= 0 -> dead          // Most urgent
    on health < 20 -> critical      // Very urgent
    on health < 50 -> wounded       // Moderate
}

4. Avoid Orphan States

Ensure all states are reachable from the initial state.

Avoid:

life_arc Broken {
    state start {
        on ready -> middle
    }

    state middle {
        on done -> end
    }

    state unreachable {}  // No transition leads here!

    state end {}
}

5. Use On-Enter for State Initialization

state combat {
    on enter {
        Character.weapon_drawn: true
        Character.combat_stance: "aggressive"
        Character.target: nearest_enemy
    }
}

Cross-References

  • Finite State Machines (FSM): Life arcs are FSMs
  • Character development: Track character growth over time
  • Quest states: Model quest progression
  • Mood systems: Model emotional states
  • Lifecycle modeling: Birth, growth, aging, death

Schedules

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.

What is a Schedule?

A schedule is a collection of time blocks that define activities throughout a day, week, season, or year:

  • Blocks: Time ranges (e.g., 06:00 - 14:00) with associated activities/behaviors
  • Temporal constraints: When blocks apply (season, day of week, month)
  • Recurrence patterns: Repeating events (e.g., “Market Day every Earthday”)
  • Composition: Schedules can extend and override other schedules
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)

Syntax

<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>+ "}"

Schedule Blocks

A schedule block defines an activity during a specific time range.

Basic Block

schedule SimpleBaker {
    block {
        06:00 - 14:00
        action: baking::prepare_and_sell
    }
}

Named Block

Named blocks support the override system:

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
    }
}

Block Content

Blocks can contain:

  1. Time range (required): HH:MM - HH:MM
  2. Action (optional): Behavior tree reference
  3. Temporal constraint (optional): When the block applies
  4. Fields (optional): Additional metadata
schedule CompleteBlock {
    block morning_work {
        06:00 - 12:00
        action: work::morning_routine
        on season summer
        location: "Outdoor Market"
    }
}

Time Ranges

Time ranges use 24-hour clock format.

Syntax

HH:MM - HH:MM
  • HH: Hour (00-23)
  • MM: Minute (00-59)
  • Optional seconds: HH:MM:SS - HH:MM:SS

Examples

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 }
}

Overnight Ranges

Blocks can span midnight:

schedule NightShift {
    block night_work {
        22:00 - 06:00  // 10 PM to 6 AM next day
        action: security::patrol
    }
}

The system interprets this as 22:00-24:00 (day 1) + 00:00-06:00 (day 2).

Actions

The action field links a schedule block to a behavior tree.

Syntax

action: <qualified-path>

Examples

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
    }
}

Qualified Paths

Actions can reference behaviors from other modules:

schedule GuardSchedule {
    block patrol {
        08:00 - 16:00
        action: behaviors::guards::patrol_route
    }

    block training {
        16:00 - 18:00
        action: combat::training::drills
    }
}

Temporal Constraints

Temporal constraints specify when a block applies (season, day of week, month, date range).

Season Constraint

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
    }
}

Season values are defined by enums in your storybook:

enum Season {
    spring
    summer
    fall
    winter
}

Day of Week Constraint

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
    }
}

Day values are typically defined by a DayOfWeek enum:

enum DayOfWeek {
    monday
    tuesday
    wednesday
    thursday
    friday
    saturday
    sunday
}

Month Constraint

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
    }
}

Date Range Constraint

schedule TouristSeason {
    block high_season {
        08:00 - 22:00
        action: tourism::busy_service
        on dates "Jun 1" .. "Sep 1"
    }
}

Note: Date format is implementation-specific. Check your runtime for supported formats.

Recurrence Patterns

Recurrence patterns define recurring events that modify the schedule.

Syntax

recurs EventName on <temporal-constraint> {
    <schedule-block>+
}

Examples

Weekly Recurring Event

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
        }
    }
}

Monthly Recurring Event

schedule TownSchedule {
    recurs CouncilMeeting on month first_monday {
        block meeting {
            18:00 - 21:00
            action: governance::council_session
        }
    }
}

Seasonal Recurring Event

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
        }
    }
}

Schedule Composition

Schedules can extend other schedules using extends, inheriting and overriding blocks.

Extends Clause

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
    }
}

Override Semantics

When extending a schedule:

  1. Named blocks with matching names override base blocks
  2. Unnamed blocks are appended
  3. Time range can change
  4. Action can change
  5. Constraints can be added/changed

Multiple Levels

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
    }
}

Resolution:

  • work block: 03:00-11:00 with baking::master_work (MasterBaker overrides BakerWork overrides BaseWork)
  • teaching block: 14:00-16:00 with baking::teach_apprentice (from MasterBaker)

Complete Examples

Simple Daily Schedule

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
    }
}

Seasonal Variation

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
    }
}

Weekly Pattern with Recurrence

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
        }
    }
}

Extended Schedule

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
    }
}

Complex Year-Round Schedule

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
        }
    }
}

Integration with Characters

Characters link to schedules using the uses schedule clause:

character Martha {
    uses schedule: BakerySchedule
}

Multiple schedules:

character Innkeeper {
    uses schedules: [WeekdaySchedule, WeekendSchedule]
}

See Characters for complete integration syntax.

Execution Semantics

Time-Based Selection

At any given time, the runtime:

  1. Evaluates temporal constraints: Which blocks apply right now?
  2. Selects matching block: First block whose time range and constraint match
  3. Executes action: Runs the associated behavior tree
  4. Repeats next tick

Priority Order

When multiple blocks could apply:

  1. Recurrences take priority over regular blocks
  2. Constraints filter blocks (season, day, month)
  3. Time ranges must overlap current time
  4. First match wins (declaration order)

Block Overlap

Blocks can overlap in time. The runtime selects the first matching block.

Example:

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
    }
}

On Wednesday at 11:00, specialized block is selected (more specific constraint).

Validation Rules

  1. Time format: Times must be valid HH:MM or HH:MM:SS (24-hour)
  2. Time order: Start time must be before end time (unless spanning midnight)
  3. Extends exists: If using extends, base schedule must be defined
  4. No circular extends: Cannot form circular dependency chains
  5. Named blocks unique: Block names must be unique within a schedule
  6. Action exists: Action references must resolve to defined behaviors
  7. Constraint values: Temporal constraint values must reference defined enums
  8. Recurrence names unique: Recurrence names must be unique within a schedule

Best Practices

1. Use Named Blocks for Overrides

Avoid:

schedule Extended extends Base {
    block { 05:00 - 13:00, action: work }  // Appends instead of overriding
}

Prefer:

schedule Extended extends Base {
    block work { 05:00 - 13:00, action: early_work }  // Overrides by name
}
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 }
}

3. Use Recurrences for Special Events

Avoid: Duplicating blocks manually

Prefer:

recurs MarketDay on day saturday {
    block market { 08:00 - 16:00, action: market_work }
}

4. Extend for Variations

Avoid: Duplicating entire schedules

Prefer:

schedule WorkerBase { ... }
schedule EarlyWorker extends WorkerBase { ... }
schedule NightWorker extends WorkerBase { ... }

5. Use Temporal Constraints for Clarity

block summer_hours {
    06:00 - 20:00
    action: long_shift
    on season summer  // Explicit and readable
}

Cross-References

  • Time-based AI: Schedules drive time-dependent behavior
  • Template inheritance: extends enables schedule reuse
  • Temporal constraints: Filter blocks by season, day, month
  • Recurrence patterns: Define repeating events
  • Composition: Build complex schedules from simple parts

Relationships

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.

What is a Relationship?

A relationship connects two or more participants and describes:

  • Participants: The entities involved (characters, institutions)
  • Roles: Optional labels (parent, employer, spouse)
  • Shared fields: Properties of the relationship itself (bond strength, duration)
  • Perspective fields: How each participant views the relationship (self/other blocks)
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 }

Syntax

<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>

Basic Relationships

Simple Two-Party Relationship

relationship Friendship {
    Martha
    Gregory
    bond: 0.8
    years_known: 15
}

Semantics:

  • Two participants: Martha and Gregory
  • Shared field: bond (strength of friendship)
  • Shared field: years_known (duration)

Multi-Party Relationship

relationship Family {
    Martha
    David
    Tommy
    Elena

    household: "Baker Residence"
    family_bond: 0.95
}

Relationships can have any number of participants.

Roles

Roles label a participant’s function in the relationship.

Syntax

ParticipantName as role_name

Examples

relationship Marriage {
    Martha as spouse
    David as spouse

    bond: 0.9
    anniversary: "2015-06-20"
}
relationship ParentChild {
    Martha as parent
    Tommy as child

    bond: 0.95
    guardianship: true
}
relationship EmployerEmployee {
    Martha as employer
    Elena as employee

    workplace: "Martha's Bakery"
    salary: 45000
}

Role Semantics

  • Labels: Roles are descriptive labels, not types
  • Multiple roles: Same person can have different roles in different relationships
  • Optional: Roles are optional—participants can be unnamed
  • Flexibility: No predefined role vocabulary—use what makes sense

Perspective Fields (Self/Other Blocks)

Perspective fields specify how each participant views the relationship. They enable asymmetric, bidirectional relationships.

Syntax

ParticipantName as role self {
    // Fields describing this participant's perspective
} other {
    // Fields describing how this participant views others
}

Self Block

The self block contains fields about how the participant experiences the relationship:

relationship ParentChild {
    Martha as parent self {
        responsibility: 1.0
        protective: 0.9
        anxiety_level: 0.6
    }
    Tommy as child
}

Martha’s perspective:

  • responsibility: She feels 100% responsible
  • protective: She’s highly protective (90%)
  • anxiety_level: Moderate anxiety about parenting

Other Block

The other block contains fields about how the participant views the other participants:

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
}

Both Blocks

relationship EmployerEmployee {
    Martha as employer self {
        authority: 0.9
        stress: 0.6
    } other {
        respect: 0.8
        trust: 0.85
    }
    Elena as employee
}

Martha’s perspective:

  • Self: She has high authority (90%), moderate stress (60%)
  • Other: She respects Elena (80%), trusts her (85%)

Asymmetric Relationships

Different participants can have different perspective fields:

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
}

Gandalf’s view:

  • He’s patient (80%), has much wisdom to share
  • Sees Frodo as having high potential but only moderately ready

Frodo’s view:

  • He’s eager but overwhelmed
  • Deeply admires Gandalf, slightly intimidated

Shared Fields

Fields declared at the relationship level (not in self/other blocks) are shared among all participants.

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
}

Common shared fields:

  • bond: Relationship strength (0.0 to 1.0)
  • years_known: Duration of relationship
  • trust: Mutual trust level
  • commitment: Commitment to the relationship
  • compatibility: How well they get along

Prose Blocks

Relationships can include prose blocks for narrative context.

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.
        ---
    }
}

Common prose tags:

  • ---perspective: How a participant views the relationship
  • ---history: Background of the relationship
  • ---dynamics: How the relationship functions
  • ---notes: Design or development notes

Note: In this syntax, perspective fields are inside participant blocks (not separate self/other blocks).

Complete Examples

Simple Friendship

relationship Friendship {
    Martha
    NeighborBaker

    bond: 0.6
    years_known: 10
}

Marriage with Roles

relationship Marriage {
    Martha as spouse
    David as spouse

    bond: 0.9
    anniversary: "2015-06-20"
    children: 2
}

Parent-Child with Perspectives

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
}

Employer-Employee

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"
}

Complex Multi-Perspective

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
}

Business Rivalry

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
}

Multi-Party Family

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.
    ---
}

Co-Owners Partnership

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
}

Participant Types

Participants can be:

  1. Characters: Most common
  2. Institutions: Organizations in relationships
  3. Locations: Less common, but valid (e.g., “GuardianOfPlace”)
relationship GuildMembership {
    Elena as member
    BakersGuild as organization

    membership_since: "2023-01-01"
    standing: "good"
    dues_paid: true
}

Field Resolution

When a relationship is resolved, fields are merged:

  1. Relationship shared fields apply to all participants
  2. Participant self blocks apply to that participant
  3. Participant other blocks describe how that participant views others

Example:

relationship Example {
    Martha as friend self {
        loyalty: 0.9
    } other {
        trust: 0.8
    }
    Gregory as friend

    bond: 0.85
}

Resolution:

  • Martha gets: loyalty: 0.9 (from self), trust: 0.8 (towards Gregory, from other), bond: 0.85 (shared)
  • Gregory gets: bond: 0.85 (shared)
  • Relationship gets: bond: 0.85

Validation Rules

  1. At least two participants: Relationships require ≥2 participants
  2. Participants exist: All participant names must reference defined entities
  3. Unique participant names: Each participant appears at most once
  4. Field type consistency: Fields must have valid value types
  5. No circular relationships: Avoid infinite relationship chains (warning)
  6. Self/other completeness: If using self/other, both blocks should be present (best practice)

Use Cases

Social Networks

relationship BakerFriendship {
    Martha
    Gregory
    bond: 0.8
}

relationship SupplierPartnership {
    Martha
    Farmer_Jenkins
    bond: 0.7
}

relationship BakeryRivalry {
    Martha
    RivalBaker
    bond: 0.2
    competitive: true
}

Family Structures

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
}

Power Dynamics

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"
}

Asymmetric Awareness

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
}

Best Practices

1. Use Descriptive Relationship Names

Avoid:

relationship R1 { ... }
relationship MarthaGregory { ... }

Prefer:

relationship Friendship { ... }
relationship ParentChild { ... }
relationship MentorApprentice { ... }

2. Use Roles for Clarity

relationship Marriage {
    Martha as spouse
    David as spouse
}

Better than:

relationship Marriage {
    Martha
    David
}

3. Shared Fields for Mutual Properties

relationship Partnership {
    Martha
    Jane

    bond: 0.9           // Mutual bond
    years_together: 5   // Shared history
}

4. Self/Other for Perspectives

relationship TeacherStudent {
    Teacher as mentor self {
        patience: 0.8
    } other {
        potential: 0.9
    }

    Student as learner self {
        motivation: 0.7
    } other {
        admiration: 0.95
    }
}

5. Prose Blocks for Rich Context

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.
    ---
}

Cross-References

  • Bidirectional modeling: self/other blocks enable asymmetric perspectives
  • Social simulation: Relationships drive character interactions
  • Narrative depth: Prose blocks embed storytelling in relationship data
  • Power dynamics: Roles and perspective fields model hierarchies
  • Emotional bonds: Bond strength and trust metrics quantify connections

Locations

Locations define places in your world – rooms, buildings, cities, landscapes, or abstract spaces. They provide spatial context for characters, events, and narratives.


Syntax

<location-decl> ::= "location" <identifier> "{" <field>* <prose-block>* "}"

A location declaration consists of:

  • The location keyword
  • A unique name (identifier)
  • A body block containing fields and optional prose blocks

Locations are one of the simpler declaration types – they hold fields and prose blocks but do not support resource linking (uses behaviors / uses schedule) like characters or institutions.


Basic Location

The simplest location has a name and descriptive fields:

location BakersBakery {
    type: bakery
    capacity: 30
    address: "14 Main Street"
    open_hours: "06:00-18:00"
}

Fields can use any value type: integers, floats, strings, booleans, enums, lists, ranges, and durations.


Fields

Location fields describe properties of the place:

location BakerHome {
    type: residence
    bedrooms: 3
    has_garden: true
    garden_size_sqft: 450.0
    residents: ["Martha", "David", "Jane"]
    comfort_level: 0.85
}

Common Field Patterns

FieldTypeDescription
typeenum/identifierCategory of the place
capacityintegerHow many can be in this place
sizeenum/floatPhysical size
coordinatesinteger/floatPosition in a world map
accessiblebooleanWhether characters can enter

These are conventions, not enforced schema. You define whatever fields are meaningful for your world.


Prose Blocks

Locations support prose blocks for rich narrative content. Prose blocks are delimited by ---tag markers:

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.
    ---
}

Prose block rules:

  • Start with ---tag_name on its own line
  • Content is free-form text (Markdown supported)
  • End with --- on its own line
  • The tag name becomes the key for retrieval
  • Multiple prose blocks per location are allowed
  • Each tag must be unique within the location

Ranges

Locations can use range values for procedural variation:

location MarketSquare {
    type: outdoor_market
    stalls: 10..25
    daily_visitors: 50..200
    noise_level: 0.4..0.9
}

When instantiated, values are selected from within the specified range. This is useful for locations that should vary across simulation runs.


Lists

Locations can hold list values:

location BakersBakery {
    type: bakery
    products: ["sourdough", "croissants", "rye bread", "cinnamon rolls"]
    equipment: ["stone oven", "mixing station", "proofing cabinet"]
}

Referencing Other Entities

Location fields can reference other declarations by name:

location BakersBakery {
    type: bakery
    owner: Martha
    neighborhood: MainStreet
    part_of: TownCenter
}

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.


Nested Structure with Fields

You can model spatial hierarchy using fields:

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
}

There is no built-in parent-child relationship for locations – you model hierarchy through conventional field names like part_of, parent, or contains.


Location with Enum Fields

Use enums for type-safe categorization:

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
}

Complete Example: Baker Family Locations

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.
    ---
}

Validation Rules

  1. Unique names: Location names must be unique within their scope
  2. Valid field values: All fields must have values that conform to value types
  3. Unique field names: No duplicate field names within a location
  4. Unique prose tags: No duplicate prose block tags within a location
  5. Valid identifiers: Location names must follow identifier rules ([a-zA-Z_][a-zA-Z0-9_]*)

Locations vs. Other Declarations

AspectLocationsInstitutionsCharacters
PurposePhysical/abstract placesOrganizations/groupsIndividuals
Resource linkingNoYesYes
Prose blocksYesYesYes
SpeciesNoNoYes
TemplatesNoNoYes

Locations are intentionally simple. They define where things happen. For who does things, use characters. For organizational structures, use institutions.


Cross-References

Institutions

Institutions define organizations, groups, and systems – entities that function like characters but represent collectives rather than individuals. Unlike locations (which model where), institutions model what structures and organizations operate in your world.


Syntax

<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>)* "]"

An institution declaration consists of:

  • The institution keyword
  • A unique name (identifier)
  • A body block containing fields, resource links, and optional prose blocks

Basic Institution

The simplest institution has a name and descriptive fields:

institution BakersGuild {
    type: trade_guild
    members: 50
    founded: "1892"
    reputation: 0.85
}

Fields

Institution fields describe properties of the organization:

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
}

Common Field Patterns

FieldTypeDescription
typeenum/identifierCategory of organization
membersintegerMembership count
foundedstringEstablishment date
reputationfloatPublic standing (0.0-1.0)
locationidentifierWhere the institution operates
leaderidentifierWho runs the institution

These are conventions – you define whatever fields make sense for your world.


Prose Blocks

Institutions support prose blocks for rich narrative documentation:

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.
    ---
}

Prose block rules:

  • Start with ---tag_name on its own line
  • Content is free-form text (Markdown supported)
  • End with --- on its own line
  • Multiple prose blocks per institution are allowed
  • Each tag must be unique within the institution

Resource Linking: Behaviors

Institutions can link to behavior trees using the uses behaviors clause. This defines what actions the institution takes as a collective entity.

Simple Behavior Linking

institution BakersGuild {
    type: trade_guild
    members: 50

    uses behaviors: [
        { tree: ManageApprentices },
        { tree: NegotiateSuppliers },
        { tree: HostEvents }
    ]
}

Each behavior link is an object with a tree field referencing a behavior tree by name or path.

institution BakersGuild {
    type: trade_guild

    uses behaviors: [
        { tree: ManageApprentices, priority: normal },
        { tree: NegotiateSuppliers, priority: high },
        { tree: HostEvents, priority: low }
    ]
}

Priority levels: low, normal (default), high, critical.

institution BakersGuild {
    type: trade_guild

    uses behaviors: [
        { tree: ManageApprentices },
        { tree: NegotiateSuppliers, priority: high },
        { tree: EmergencyMeeting, when: reputation < 0.3, priority: critical }
    ]
}

The when clause takes an expression that determines when the behavior activates.

FieldRequiredTypeDescription
treeYespathReference to a behavior tree
priorityNopriority levelExecution priority (default: normal)
whenNoexpressionActivation condition

Resource Linking: Schedules

Institutions can link to schedules using the uses schedule or uses schedules clause.

Single Schedule

institution BakersGuild {
    type: trade_guild
    uses schedule: GuildOperatingHours
}

Multiple Schedules

institution BakersGuild {
    type: trade_guild
    uses schedules: [WeekdaySchedule, WeekendSchedule, HolidaySchedule]
}

Complete Syntax Example

An institution using all available features:

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.
    ---
}

Membership Modeling

Institutions themselves don’t have a built-in membership list. Instead, model membership through character fields or relationships:

Via Character Fields

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
}

Via Relationships

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
}

Institutions vs. Characters

Both institutions and characters can use behaviors and schedules, but they serve different purposes:

AspectInstitutionsCharacters
RepresentsOrganizations, collectivesIndividuals
SpeciesNoYes (optional)
TemplatesNoYes (optional)
uses behaviorsYesYes
uses scheduleYesYes
Prose blocksYesYes
Participation in relationshipsYes (via name)Yes (via name)

Use institutions when you need an entity that acts collectively: a guild votes, a government issues decrees, a school enrolls students.


Use Cases

  • Organizations: Guilds, companies, governments, religions
  • Social structures: Families, tribes, castes, classes
  • Systems: Economic systems, magical systems, legal frameworks
  • Abstract entities: Dream logic, fate, cultural norms
  • Collectives: Military units, sports teams, musical ensembles

Complete Example: Baker Family World

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.
    ---
}

Validation Rules

  1. Unique names: Institution names must be unique within their scope
  2. Valid field values: All fields must have values conforming to value types
  3. Unique field names: No duplicate field names within an institution
  4. Unique prose tags: No duplicate prose block tags within an institution
  5. Valid behavior links: Each behavior link must have a tree field
  6. Valid priority levels: Priority must be low, normal, high, or critical
  7. Valid schedule references: Schedule names in uses schedule/uses schedules must be valid identifiers
  8. Valid identifiers: Institution names must follow identifier rules ([a-zA-Z_][a-zA-Z0-9_]*)

Cross-References

Other Declarations

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.


Templates

Templates are reusable field sets that characters inherit using the from keyword. Unlike species (which define what an entity is), templates define what an entity has—capabilities, traits, and characteristics.

Syntax

<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>

Basic Template

template Warrior {
    strength: 10..20
    dexterity: 8..15
    weapon_proficiency: 0.7..1.0
}

Characters inheriting this template get these fields with values selected from the specified ranges.

Template Includes

Templates can include other templates to compose functionality:

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
}

Semantics:

  • include SkilledWorker brings in all fields from that template
  • Fields are merged left-to-right (later overrides earlier)
  • Transitive includes supported
  • Multiple includes allowed: include A, B, C

Range Values

Templates can specify ranges for procedural variation:

template Villager {
    age: 18..65
    wealth: 10..100
    height: 150.0..190.0
    disposition: 0.0..1.0
}

Range syntax:

  • Integer ranges: min..max (inclusive)
  • Float ranges: min..max (inclusive)
  • Both bounds must be same type
  • min ≤ max required

When a character uses this template, the runtime selects specific values within each range.

Strict Mode

Strict templates enforce that characters using them can only have fields defined in the template:

template RecipeCard strict {
    include SkilledWorker

    recipe_name: string
    difficulty: Difficulty
    prep_time_minutes: 10..180
    requires_starter: false
}

Strict semantics:

  • Characters using strict templates cannot add extra fields
  • All template fields must be present in the character
  • Enables type safety for well-defined schemas
  • Use for controlled domains (game cards, rigid categories)

Non-strict (default):

template Flexible {
    base_stat: 10
}

character Custom from Flexible {
    base_stat: 15      // Override
    extra_field: 42    // Allowed in non-strict
}

Strict:

template Rigid strict {
    required_stat: 10
}

character Constrained from Rigid {
    required_stat: 15  // OK: Override
    extra_field: 42    // ERROR: Not allowed in strict template
}

Resource Linking in Templates

Templates can link to behaviors and schedules (v0.2.0+):

template BakeryStaffMember
    uses behaviors: DailyBakingRoutine, CustomerService
    uses schedule: BakerySchedule
{
    include SkilledWorker

    on_shift: true
    orders_completed: 0..1000
    current_station: 0..4
}

Characters inheriting this template automatically get the linked behaviors and schedule.

Templates vs. Species

AspectTemplates (from)Species (:)
SemanticsWhat entity hasWhat entity is
CardinalityMultiple inheritanceSingle inheritance
RangesAllowedNot allowed
Strict modeSupportedNot supported
Use caseCompositional traitsOntological identity

Example combining both:

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
}

Validation Rules

  1. Includes exist: All included templates must be defined
  2. No circular includes: Cannot form cycles
  3. Range validity: min ≤ max for all ranges
  4. Strict enforcement: Strict templates reject extra fields in characters
  5. Resource links valid: Behavior/schedule references must resolve

Institutions

Institutions define organizations, groups, and systems—entities that function like characters but represent collectives rather than individuals.

Syntax

<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>* "}"

Basic Institution

institution BakersGuild {
    type: trade_guild
    members: 50
    founded: "1450-03-15"
    reputation: 0.85
}

Institutions with Fields

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.
    ---
}

Resource Linking

Institutions can use behaviors and schedules:

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.
    ---
}

Prose Blocks

Institutions support rich narrative documentation:

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.
    ---
}

Use Cases

  • Organizations: Guilds, companies, governments
  • Systems: Magical systems, physical laws, economies
  • Social structures: Families, tribes, castes
  • Abstract entities: Dream logic, fate, chaos

Validation Rules

  1. Unique names: Institution names must be unique
  2. Resource links valid: Behaviors/schedules must exist
  3. Field types: All fields must have valid values

Locations

Locations define places in your world—rooms, buildings, cities, landscapes, or abstract spaces.

Syntax

<location-decl> ::= "location" <identifier> <body>

<body> ::= "{" <field>* <prose-block>* "}"

Basic Location

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.
    ---
}

Location with Structure

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.
    ---
}

Geographic Hierarchy

Locations can reference other locations:

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.
    ---
}

Location with State

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.
    ---
}

Use Cases

  • Physical places: Cities, buildings, rooms
  • Geographic features: Mountains, rivers, forests
  • Abstract spaces: Dream realms, pocket dimensions
  • Game boards: Arenas, dungeons, maps

Validation Rules

  1. Unique names: Location names must be unique
  2. Field types: All fields must have valid values
  3. References valid: Location references (like part_of) should resolve

Species

Species define the fundamental ontological categories of beings. A species represents what an entity is at its core—human, dragon, sentient tree, animated playing card.

Syntax

<species-decl> ::= "species" <identifier> <includes-clause>? <body>

<includes-clause> ::= "includes" <identifier> ("," <identifier>)*

<body> ::= "{" <field>* <prose-block>* "}"

Basic Species

species Human {
    lifespan: 70

    ---description
    Bipedal mammals with complex language and tool use.
    Highly variable in cultures and capabilities.
    ---
}

Species with Includes

Species can include other species for composition:

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.
    ---
}

Field Resolution with Includes

When a species includes others, fields are merged in declaration order:

  1. Base species (leftmost in includes)
  2. Middle species
  3. Rightmost species
  4. Current species (highest priority)

Example:

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)

Species vs. Templates

AspectSpecies (:)Templates (from)
Question“What is it?”“What traits does it have?”
CardinalityOne per characterZero or more
Inheritanceincludes (species → species)Characters inherit from templates
VariationConcrete defaultsRanges allowed
Examplespecies Humantemplate Warrior

When to use species:

species Dragon {
    lifespan: 1000
    can_fly: true
    breathes_fire: true
}

character Smaug: Dragon {
    age: 850  // Smaug IS a Dragon
}

When to use templates:

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
}

Design Pattern: Prefer Composition Over Deep Hierarchies

Avoid:

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!

Prefer:

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 { ... }

Validation Rules

  1. Unique names: Species names must be unique
  2. No circular includes: Cannot form cycles
  3. Includes exist: All included species must be defined
  4. Field types: All fields must have valid values

Enums

Enums define controlled vocabularies—fixed sets of named values. They enable type-safe categorization and validation.

Syntax

<enum-decl> ::= "enum" <identifier> "{" <variant>+ "}"

<variant> ::= <identifier> ","?

Basic Enum

enum Size {
    tiny,
    small,
    normal,
    large,
    huge
}

Using Enums

Enums are used as field values throughout the system:

character Martha: Human {
    skill_level: master     // References SkillLevel enum
    specialty: sourdough    // References Specialty enum
}

Common Enum Patterns

Emotional States:

enum EmotionalState {
    curious,
    frightened,
    confused,
    brave,
    angry,
    melancholy,
    amused
}

Card Suits:

enum CardSuit {
    hearts,
    diamonds,
    clubs,
    spades
}

enum CardRank {
    two, three, four, five, six, seven, eight, nine, ten,
    knave, queen, king
}

Time States:

enum TimeState {
    normal,
    frozen,
    reversed,
    accelerated
}

Government Types:

enum GovernmentType {
    monarchy,
    democracy,
    anarchy,
    tyranny,
    oligarchy
}

enum GovernmentStyle {
    absolute_tyranny,
    benevolent_dictatorship,
    constitutional_monarchy,
    direct_democracy,
    representative_democracy
}

Calendar Enums (Configurable)

Define custom calendars for your world:

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
}

Custom calendars:

enum EightSeasons {
    deep_winter,
    late_winter,
    early_spring,
    late_spring,
    early_summer,
    late_summer,
    early_fall,
    late_fall
}

Validation Integration

Enums enable compile-time validation:

enum Size {
    tiny, small, normal, large, huge
}

character Martha {
    skill_level: medium  // ERROR: 'medium' not in SkillLevel enum
}

Use Cases

  • Controlled vocabularies: Prevent typos and invalid values
  • Schema constraints: Temporal systems, card decks, status types
  • Type safety: Catch errors at compile time
  • Documentation: Enumerate all valid options

Validation Rules

  1. Unique enum names: Enum names must be unique
  2. Unique variants: Variant names must be unique within the enum
  3. Non-empty: Enums must have at least one variant
  4. Valid identifiers: Variants must follow identifier rules

Use Statements

Use statements import definitions from other files, enabling modular organization and code reuse.

Syntax

<use-decl> ::= "use" <use-path> <use-kind>

<use-path> ::= <identifier> ("::" <identifier>)*

<use-kind> ::= ";"                                   // Single import
             | "::{" <identifier> ("," <identifier>)* "}" ";"  // Grouped import
             | "::*" ";"                             // Wildcard import

Single Import

use schema::beings::Human;

Imports the Human species from schema/beings.sb.

Grouped Import

use schema::core_enums::{Size, EmotionalState};

Imports multiple items from the same module.

Wildcard Import

use schema::beings::*;

Imports all public items from schema/beings.sb.

Qualified Paths

Module structure:

examples/alice-in-wonderland/
├─ schema/
│   ├─ core_enums.sb
│   ├─ templates.sb
│   └─ beings.sb
└─ world/
    ├─ characters/
    │   └─ alice.sb
    └─ locations/
        └─ wonderland_places.sb

In 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
}

Path Resolution

  1. Relative to file’s module path: Imports are resolved relative to the current file’s location
  2. Module hierarchy: schema::beings maps to schema/beings.sb
  3. Transitive imports: Imported modules can themselves import others

Common Patterns

Schema organization:

// 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};

Template composition:

// In schema/templates.sb
template SkilledWorker { ... }
template Baker {
    include SkilledWorker
    ...
}

// In world/characters/martha.sb
use schema::templates::Baker;

Cross-file references:

// In world/characters/martha.sb
character Martha { ... }

// In world/relationships/bakery.sb
use world::characters::Martha;

relationship MarthaAndJane {
    Martha
    Jane
}

Validation Rules

  1. Path exists: Imported paths must reference defined modules/items
  2. No circular imports: Modules cannot form circular dependency chains
  3. Valid identifiers: All path segments must be valid identifiers
  4. Grouped import validity: All items in {} must exist

Best Practices

1. Group related imports:

use schema::core_enums::{SkillLevel, Specialty, Confidence};
use schema::beings::{Human, Cat};

2. Explicit over wildcard:

// Prefer:
use schema::core_enums::{SkillLevel, Specialty};

// Over:
use schema::core_enums::*;  // Imports everything

3. Organize by hierarchy:

// Schema imports first
use schema::core_enums::SkillLevel;
use schema::templates::Baker;

// Then world imports
use world::characters::Martha;

4. Use qualified paths for clarity:

character Elena: Human from Baker, Apprentice {
    // Human is species (from schema::beings)
    // Baker, Apprentice are templates (from schema::templates)
}

Cross-References

  • Modularity: Use statements enable file organization
  • Composition: Templates provide trait-based composition
  • Type safety: Enums prevent invalid values
  • World-building: Locations and institutions model environments
  • Ontology: Species define entity essence

Expression Language

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.

What are Expressions?

Expressions are logical statements that evaluate to true or false. They combine:

  • Literals: Numbers, strings, booleans
  • Identifiers: References to fields and entities
  • Comparisons: ==, !=, <, <=, >, >=
  • Logical operators: and, or, not
  • Field access: self.field, other.field
  • Quantifiers: forall, exists (for collections)

Syntax

<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>

Literals

Integer Literals

42
-7
0
1000

Float Literals

3.14
-0.5
0.0
100.25

String Literals

"Martha"
"Sourdough takes patience."
"active"

Strings are enclosed in double quotes. Escape sequences: \n, \t, \\, \".

Boolean Literals

true
false

Identifiers

Identifiers reference fields or entities.

Simple Identifiers

health
enemy_count
is_ready

Qualified Paths

Martha.skill_level
Character.emotional_state

Comparison Operators

Equality: ==

Tests if two values are equal.

name == "Martha"
count == 5
status == active

Type compatibility:

  • Both operands must be the same type
  • Works with: int, float, string, bool, enum values

Inequality: !=

Tests if two values are not equal.

name != "Gregory"
health != 0
ready != true

Less Than: <

Tests if left operand is less than right.

health < 20
age < 18
distance < 10.0

Valid types: int, float

Less Than or Equal: <=

health <= 50
count <= max_count

Greater Than: >

strength > 10
bond > 0.5

Greater Than or Equal: >=

age >= 21
score >= 100

Logical Operators

AND: and

Both operands must be true.

health < 50 and has_potion
is_ready and not is_busy
age >= 18 and age < 65

Evaluation: Short-circuit (if left is false, right is not evaluated)

OR: or

At least one operand must be true.

is_day or is_lit
health < 20 or surrounded
enemy_count == 0 or all_enemies_dead

Evaluation: Short-circuit (if left is true, right is not evaluated)

Operator Precedence

From highest to lowest:

  1. Parentheses: (...)
  2. Unary: not, -
  3. Comparisons: ==, !=, <, <=, >, >=
  4. AND: and
  5. OR: or

Examples:

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

Unary Operators

NOT: not

Inverts a boolean value.

not is_ready
not (health < 20)
not enemy_nearby and safe

Negation: -

Negates a numeric value.

-health
-10
-(max_value - current_value)

Field Access

Direct Field Access

health
bond
emotional_state

References a field on the current entity.

Dot Access

Martha.skill_level
Character.emotional_state
enemy.health

Access fields on other entities.

Self Access

In relationships and certain contexts, self refers to the current participant:

self.bond
self.responsibility
self.trust

Use case: Relationship transitions, symmetric queries

Other Access

In relationships, other refers to other participants:

other.bond
other.aware_of_mentor
other.respect

Use case: Relationship queries with perspective

Example in Life Arcs

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 {}
}

Quantifiers

Quantifiers test conditions over collections.

ForAll: forall

Tests if a condition holds for all elements in a collection.

forall e in enemies: e.defeated
forall item in inventory: item.weight < 10

Syntax:

forall <variable> in <collection>: <predicate>

Semantics:

  • Returns true if predicate is true for every element
  • Returns true for empty collections (vacuously true)

Examples:

// 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

Exists: exists

Tests if a condition holds for at least one element.

exists e in enemies: e.is_hostile
exists item in inventory: item.is_healing_potion

Syntax:

exists <variable> in <collection>: <predicate>

Semantics:

  • Returns true if predicate is true for any element
  • Returns false for empty collections

Examples:

// 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

Nested Quantifiers

Quantifiers can nest:

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

Usage in Context

Life Arc Transitions

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
    }
}

Behavior Tree Conditions

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
        }
    }
}

Behavior Tree Conditions

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
        }
    }
}

Type System

Type Compatibility

Comparisons require compatible types:

OperatorLeft TypeRight TypeValid?
==, !=intint
==, !=floatfloat
==, !=stringstring
==, !=boolbool
==, !=enumsame enum
==, !=intfloat
<, <=, >, >=intint
<, <=, >, >=floatfloat
<, <=, >, >=stringstring

Implicit Coercion

None. Storybook has no implicit type coercion. All comparisons must be between compatible types.

Error:

count == "5"  // Error: int vs string
health < true  // Error: int vs bool

Correct:

count == 5
health < 50

Special Keyword: is

The is keyword provides syntactic sugar for equality with enum values:

// Instead of:
status == active

// You can write:
status is active

More examples:

name is "Martha"
skill_level is master
emotional_state is focused

This is purely syntactic—is and == are equivalent.

Complete Examples

Simple Conditions

health < 20
enemy_nearby
not is_ready
count > 5

Complex Conditions

(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

Life Arc with Complex Conditions

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
    }
}

Behavior with Quantifiers

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
        }
    }
}

Relationship Query

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 {}
}

Validation Rules

  1. Type consistency: Both sides of comparison must be compatible types
  2. Boolean context: Logical operators (and, or, not) require boolean operands
  3. Field existence: Referenced fields must exist on the entity
  4. Collection validity: Quantifiers require collection-typed expressions
  5. Variable scope: Quantifier variables only valid within their predicate
  6. No division by zero: Arithmetic operations must not divide by zero
  7. Enum validity: Enum comparisons must reference defined enum values

Best Practices

1. Use Parentheses for Clarity

Avoid:

health < 50 or is_poisoned and has_antidote

Prefer:

(health < 50 or is_poisoned) and has_antidote

2. Break Complex Conditions

Avoid:

on (health < 20 and not has_potion) or (surrounded and not has_escape) or (enemy_count > 10 and weapon_broken) -> desperate

Prefer:

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
}

3. Name Complex Conditions

For repeated complex conditions, consider using intermediate fields:

Instead of:

on health < (max_health * 0.2) and enemy_count > 5 -> flee

Consider:

// In character definition:
critically_wounded: health < (max_health * 0.2)
outnumbered: enemy_count > 5

// In life arc:
on critically_wounded and outnumbered -> flee

4. Use is for Enums

Prefer:

status is active
emotional_state is focused

Over:

status == active
emotional_state == focused

5. Quantifiers for Collections

Avoid:

// Manual checks for each element
if enemy1.defeated and enemy2.defeated and enemy3.defeated

Prefer:

if forall enemy in enemies: enemy.defeated

Cross-References

  • Type safety: Strong typing prevents type errors at compile time
  • Short-circuit evaluation: AND/OR operators optimize evaluation
  • Quantifiers: Enable expressive collection queries
  • Field access: Context-sensitive (self, other) for relationships
  • Boolean algebra: Standard logical operators with expected semantics

Values

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.

Value Types Overview

Storybook supports 12 value types:

TypeExampleUse Case
Int42, -7Quantities, IDs, counts
Float3.14, -0.5Measurements, probabilities
String"Hello", "Martha"Text, names, descriptions
Booltrue, falseFlags, switches
Time14:30, 09:15:30Clock times, schedule blocks
Duration2h30m, 45sTime intervals
Range20..40, 0.5..1.0Template variation bounds
IdentifierMartha, items::swordReferences to other declarations
List[1, 2, 3], ["a", "b"]Collections
Object{x: 10, y: 20}Structured data
ProseBlock---tag content ---Long-form narrative
OverrideTemplateX with {...}Template instantiation with modifications

Integer

Signed 64-bit integers.

Syntax

<int> ::= ["-"] <digit>+
<digit> ::= "0".."9"

Examples

character Hero {
    age: 25
    gold: 1500
    reputation: -10
}

Range

  • Minimum: -9,223,372,036,854,775,808
  • Maximum: 9,223,372,036,854,775,807

Use Cases

  • Counts (items, population)
  • Identifiers (IDs, keys)
  • Scores (reputation, alignment)
  • Whole quantities (gold pieces, HP)

Float

64-bit floating-point numbers (IEEE 754 double precision).

Syntax

<float> ::= ["-"] <digit>+ "." <digit>+
          | ["-"] <digit>+ ("e" | "E") ["+"|"-"] <digit>+

Examples

character Wizard {
    mana: 100.0
    spell_power: 1.5
    corruption: 0.03
    precision: 1e-6
}

Special Values

  • No NaN or Infinity support (use validation to prevent)
  • Negative zero (-0.0) equals positive zero (0.0)

Use Cases

  • Probabilities (0.0 to 1.0)
  • Percentages (0.0 to 100.0)
  • Measurements with precision
  • Ratios and scaling factors

String

UTF-8 encoded text enclosed in double quotes.

Syntax

<string> ::= '"' <string-char>* '"'
<string-char> ::= <any-char-except-quote-or-backslash>
                | <escape-sequence>
<escape-sequence> ::= "\n" | "\r" | "\t" | "\\" | "\""

Escape Sequences

  • \n - Newline
  • \r - Carriage return
  • \t - Tab
  • \\ - Backslash
  • \" - Double quote

Examples

character Martha {
    name: "Martha Baker"
    greeting: "Fresh from the oven!"
    multiline: "Line 1\nLine 2\nLine 3"
    quote: "She said, \"The bread is ready!\""
}

Limitations

  • No raw strings (r“…“) or multi-line literals
  • For long text, use prose blocks
  • Maximum length: Implementation-defined (typically several MB)

Use Cases

  • Character names
  • Short descriptions
  • Dialogue snippets
  • Enum-like values (before proper enums)

Boolean

Logical true/false values.

Syntax

<bool> ::= "true" | "false"

Examples

character Guard {
    is_awake: true
    has_seen_player: false
    loyal_to_king: true
}

Use Cases

  • Feature flags (can_fly, is_hostile)
  • State tracking (door_open, quest_complete)
  • Conditions (is_friendly, accepts_bribes)

Time

Clock time in 24-hour format.

Syntax

<time> ::= <hour> ":" <minute>
         | <hour> ":" <minute> ":" <second>

<hour> ::= "0".."23"
<minute> ::= "0".."59"
<second> ::= "0".."59"

Examples

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
}

Validation

  • Hours: 0-23 (24-hour format)
  • Minutes: 0-59
  • Seconds: 0-59 (optional)
  • No AM/PM notation
  • No timezone support (context-dependent)

Use Cases

  • Schedule blocks (see Schedules)
  • Character routines
  • Event timing
  • Time-based conditions

Duration

Time intervals with hour/minute/second components.

Syntax

<duration> ::= <duration-component>+

<duration-component> ::= <number> ("h" | "m" | "s")

<number> ::= <digit>+

Examples

character Traveler {
    journey_time: 2h30m
    rest_needed: 8h
    sprint_duration: 45s
}

behavior TimedAction {
    choose {
        timeout(5m) {
            CompleteObjective
        }

        cooldown(30s) {
            UseSpecialAbility
        }
    }
}

Components

  • h - Hours
  • m - Minutes
  • s - Seconds

Can combine multiple components: 1h30m15s

Validation

  • All components non-negative
  • No fractional components (1.5h not allowed; use 1h30m)
  • Can specify same unit multiple times: 90m = 1h30m (normalized)

Use Cases

  • Behavior tree timeouts/cooldowns
  • Travel times
  • Cooldown periods
  • Event durations

Range

Numeric range with inclusive bounds (for templates only).

Syntax

<range> ::= <value> ".." <value>

Both bounds must be the same type (both int or both float).

Examples

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
}

Semantics

  • Templates only: Ranges are only valid in templates
  • Instantiation: When a template is used, a specific value within the range is selected
  • Inclusive bounds: Both min and max are included
  • Order matters: min must be ≤ max

Validation

  • Both bounds same type
  • minmax
  • Cannot use ranges in character/species/etc. (only templates)

Use Cases

  • Template variation
  • Procedural generation
  • Random NPC attributes
  • Loot table ranges

Identifier

Reference to another declaration by qualified path.

Syntax

<identifier> ::= <simple-name>
               | <qualified-path>

<simple-name> ::= <ident>

<qualified-path> ::= <ident> ("::" <ident>)+

Examples

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
}

Resolution

  • Unqualified: Searches current module, then imports
  • Qualified: module::submodule::Name
  • Validation: Compiler ensures all references resolve

Use Cases

  • Species typing (: Species)
  • Template inheritance (from Template)
  • Behavior tree references
  • Schedule references
  • Relationship participants
  • Cross-references

List

Ordered collection of values.

Syntax

<list> ::= "[" "]"
         | "[" <value> ("," <value>)* "]"

Examples

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"}
    ]
}

Type Constraints

  • Homogeneous preferred: All elements should be the same type
  • Heterogeneous allowed: Mixed types permitted but discouraged
  • Empty lists: [] is valid

Operations

Lists are immutable at declaration time. Runtime list operations depend on the execution environment.

Use Cases

  • Collections (inventory, spells, party members)
  • References (multiple behaviors, schedules, participants)
  • Enum-like sets (before proper enums)

Object

Structured data with named fields.

Syntax

<object> ::= "{" "}"
           | "{" <field> ("," <field>)* "}"

<field> ::= <identifier> ":" <value>

Examples

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
        }
    }
}

Nesting

Objects can be nested arbitrarily deep:

character Complex {
    deep: {
        level1: {
            level2: {
                level3: {
                    value: 42
                }
            }
        }
    }
}

Use Cases

  • Structured data (position, stats)
  • Nested configurations
  • Inline data structures
  • Complex field values

Prose Blocks

Long-form narrative text with semantic tags.

Syntax

<prose-block> ::= "---" <tag> <content> "---"

<tag> ::= <identifier>

<content> ::= <any-text-except-triple-dash>

Common Tags

  • ---description: General description
  • ---backstory: Character history
  • ---appearance: Physical description
  • ---personality: Behavioral traits
  • ---motivation: Goals and desires
  • ---notes: Meta-commentary
  • ---ecology: Species habitat/behavior
  • ---culture: Social structures
  • ---narrative: Story context

Examples

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.
    ---
}

Formatting

  • Leading/trailing whitespace is preserved
  • No escape sequences needed
  • Can contain any characters except --- on its own line
  • Indentation is significant for readability but not semantics

Multiple Prose Blocks

A single declaration can have multiple prose blocks with different tags:

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.
    ---
}

Use Cases

  • Character backstories
  • Species descriptions
  • World-building flavor text
  • Design notes
  • Narrative context

Override

Template instantiation with field modifications.

Syntax

<override> ::= <qualified-path> "with" "{" <override-op>* "}"

<override-op> ::= <identifier> ":" <value>           // Set
                | "remove" <identifier>              // Remove
                | "append" <identifier> ":" <value>  // Append (for lists)

Examples

Basic Override

template BaseWarrior {
    strength: 10
    dexterity: 8
    weapon: "Sword"
}

character StrongWarrior {
    stats: BaseWarrior with {
        strength: 15  // Override strength
    }
}

Remove Fields

template FullEquipment {
    helmet: "Iron"
    chest: "Plate"
    legs: "Mail"
    boots: "Leather"
}

character LightFighter {
    equipment: FullEquipment with {
        remove helmet
        remove chest
    }
}

Append to Lists

template BasicSpells {
    spells: ["Fireball", "Shield"]
}

character AdvancedMage {
    magic: BasicSpells with {
        append spells: "Teleport"
        append spells: "Lightning"
    }
    // Result: ["Fireball", "Shield", "Teleport", "Lightning"]
}

Complex Override

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
    }
}

Operations

set (default)

Replace a field’s value:

field_name: new_value

remove

Delete a field from the template:

remove field_name

append

Add to a list field (field must be a list):

append field_name: value_to_add

Validation

  • Base template must exist
  • Overridden fields must exist in template
  • Removed fields must exist in template
  • Appended fields must be lists
  • Type compatibility enforced

Use Cases

  • Customizing template instances
  • Procedural character generation
  • Variant creation
  • Data composition

Type Coercion

Storybook has no implicit type coercion. All type conversions must be explicit.

Not allowed:

character Wrong {
    count: "42"  // Error: expected int, got string
    flag: 1      // Error: expected bool, got int
}

Correct:

character Right {
    count: 42
    flag: true
}

Null and Optional

Storybook does not have null. For optional values, use:

  1. Template ranges with 0 as lower bound:

    template MaybeWeapon {
        weapon_count: 0..5
    }
    
  2. Boolean flags:

    character Guard {
        has_weapon: false
    }
    
  3. Empty lists:

    character Unarmed {
        weapons: []
    }
    

Summary Table

TypeMutable?Comparable?Valid in Templates?Notes
IntNoYesYes64-bit signed
FloatNoYesYes64-bit IEEE 754
StringNoYesYesUTF-8
BoolNoYesYestrue/false
TimeNoYesNoHH:MM or HH:MM:SS
DurationNoYesNoCompounds (2h30m)
RangeNoNoYes (only)Template variation
IdentifierNoYesYesDeclaration reference
ListNoYesYesOrdered collection
ObjectNoYesYesNamed fields
ProseBlockNoNoYesNarrative text
OverrideNoNoYesTemplate modification

Cross-References

  • Immutability: All values are immutable at declaration time
  • Type safety: Strong static typing with no implicit coercion
  • Structural equivalence: Objects/lists compared by structure, not identity
  • Prose as data: Narrative text is first-class data, not comments

Validation Rules

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.

Validation Layers

Storybook validation happens in four stages:

  1. Lexical: Tokenization of raw text (invalid characters, malformed literals)
  2. Syntactic: Grammar structure (missing braces, wrong keyword order)
  3. Semantic: Cross-reference resolution, type checking, field merging
  4. Domain: Narrative-specific constraints (bond ranges, schedule overlaps)

Errors at earlier stages prevent later stages from running.

Character Validation

Required Rules

RuleDescriptionSeverity
Unique nameCharacter names must be unique within their moduleError
Species existsIf : Species is used, the species must be definedError
Templates existAll templates in from clause must be definedError
No circular inheritanceTemplate chains cannot form cyclesError
Field type consistencyField values must match expected typesError
Behavior trees existAll uses behaviors references must resolveError
Schedules existAll uses schedule references must resolveError
Prose tag uniquenessEach prose tag can appear at most once per characterError

Examples

Species not found:

character Martha: Hobbit {  // Error: species 'Hobbit' not defined
    age: 34
}

Fix: Define the species or correct the reference:

species Hobbit {
    lifespan: 130
}

character Martha: Hobbit {
    age: 34
}

Duplicate character name:

character Martha { age: 34 }
character Martha { age: 36 }  // Error: duplicate character name 'Martha'

Template Validation

RuleDescriptionSeverity
Unique nameTemplate names must be unique within their moduleError
Includes existAll included templates must be definedError
No circular includesInclude chains cannot form cyclesError
Range validityRange bounds must satisfy min <= maxError
Range type matchBoth bounds of a range must be the same typeError
Strict enforcementCharacters using strict templates cannot add extra fieldsError
Resource links validBehavior/schedule references must resolveError

Examples

Invalid range:

template BadRange {
    age: 65..18  // Error: range min (65) must be <= max (18)
}

Strict template violation:

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'
}

Behavior Tree Validation

RuleDescriptionSeverity
At least one nodeBehavior body must contain at least one nodeError
Composite childrenchoose and then require at least one childError
Decorator childDecorators require exactly one childError
Subtree existsinclude must reference a defined behaviorError
Expression validityCondition expressions must be well-formedError
Duration formatDecorator durations must be valid (e.g., 5s, 10m)Error
Repeat count validrepeat N requires N >= 0Error
Repeat range validrepeat min..max requires 0 <= min <= maxError
Retry count validretry N requires N >= 1Error

Examples

Empty composite:

behavior Empty {
    choose options {
        // Error: 'choose' requires at least one child
    }
}

Invalid subtree reference:

behavior Main {
    include NonExistentBehavior  // Error: behavior 'NonExistentBehavior' not defined
}

Life Arc Validation

RuleDescriptionSeverity
At least one stateLife arc must contain at least one stateError
Unique state namesState names must be unique within the life arcError
Valid transitionsTransition targets must reference defined statesError
Expression validityTransition conditions must be well-formedError
Field targets validOn-enter field references must resolveError
Reachable statesAll states should be reachable from initial stateWarning

Examples

Invalid transition target:

life_arc Broken {
    state active {
        on timer_expired -> nonexistent  // Error: state 'nonexistent' not defined
    }
}

Unreachable state (warning):

life_arc HasOrphan {
    state start {
        on ready -> middle
    }

    state middle {
        on done -> end
    }

    state orphan {}  // Warning: state 'orphan' is not reachable

    state end {}
}

Schedule Validation

RuleDescriptionSeverity
Time formatTimes must be valid HH:MM or HH:MM:SSError
Extends existsBase schedule must be definedError
No circular extendsSchedule chains cannot form cyclesError
Named blocks uniqueBlock names must be unique within a scheduleError
Action references validAction references must resolve to defined behaviorsError
Constraint values validTemporal constraint values must reference defined enumsError
Recurrence names uniqueRecurrence names must be unique within a scheduleError

Examples

Invalid time format:

schedule Bad {
    block work {
        25:00 - 17:00  // Error: invalid hour '25'
        action: work
    }
}

Circular extends:

schedule A extends B { }
schedule B extends A { }  // Error: circular schedule extension detected

Relationship Validation

RuleDescriptionSeverity
At least two participantsRelationships require >= 2 participantsError
Participants existAll participant names must reference defined entitiesError
Unique participantsEach participant appears at most onceError
Field type consistencyFields must have valid value typesError

Examples

Too few participants:

relationship Lonely {
    Martha  // Error: relationship requires at least 2 participants
    bond: 0.5
}

Species Validation

RuleDescriptionSeverity
Unique nameSpecies names must be unique within their moduleError
No circular includesInclude chains cannot form cyclesError
Includes existAll included species must be definedError
Field type consistencyFields must have valid valuesError
Prose tag uniquenessEach prose tag can appear at most onceError

Enum Validation

RuleDescriptionSeverity
Unique enum nameEnum names must be unique within their moduleError
Unique variantsVariant names must be unique within the enumError
Non-emptyEnums must have at least one variantError
Valid identifiersVariants must follow identifier rulesError

Examples

Duplicate variant:

enum Size {
    tiny,
    small,
    small,  // Error: duplicate variant 'small' in enum 'Size'
    large
}

Institution and Location Validation

RuleDescriptionSeverity
Unique nameNames must be unique within their moduleError
Resource links validBehavior/schedule references must resolveError
Field type consistencyFields must have valid valuesError

Expression Validation

Expressions are validated wherever they appear (life arc transitions, behavior tree conditions, if decorators).

RuleDescriptionSeverity
Type consistencyBoth sides of comparison must have compatible typesError
Boolean contextLogical operators require boolean operandsError
Field existenceReferenced fields must exist on the entityError
Collection validityQuantifiers require collection-typed expressionsError
Variable scopeQuantifier variables only valid within their predicateError
Enum validityEnum comparisons must reference defined valuesError

Examples

Type mismatch:

life_arc TypeError {
    state checking {
        on count == "five" -> done  // Error: cannot compare int with string
    }

    state done {}
}

Use Statement Validation

RuleDescriptionSeverity
Path existsImported paths must reference defined modules/itemsError
No circular importsModules cannot form circular dependency chainsError
Valid identifiersAll path segments must be valid identifiersError
Grouped import validityAll items in {} must exist in the target moduleError

Examples

Missing import:

use schema::nonexistent::Thing;  // Error: module 'schema::nonexistent' not found

Cross-File Validation

When resolving across multiple .sb files, the compiler performs additional checks:

RuleDescriptionSeverity
All references resolveCross-file references must find their targetsError
No naming conflictsDeclarations must not collide across files in the same moduleError
Import visibilityOnly public declarations can be importedError

Common Error Patterns

Missing Definitions

The most common error is referencing something that does not exist:

character Martha: Human from Baker {
    specialty: sourdough
}

If Human, Baker, or the sourdough enum variant are not defined or imported, the compiler will report an error. Fix by adding the appropriate use statements:

use schema::core_enums::{SkillLevel, Specialty};
use schema::templates::Baker;
use schema::beings::Human;

character Martha: Human from Baker {
    specialty: sourdough
}

Circular Dependencies

Circular references are rejected at every level:

  • Templates including each other
  • Species including each other
  • Schedules extending each other
  • Modules importing each other

Break cycles by restructuring into a hierarchy or extracting shared parts into a common module.

Type Mismatches

Storybook has no implicit type coercion. Ensure values match their expected types:

// 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
}

Validation Summary

DeclarationKey Constraints
CharacterUnique name, valid species/templates, no circular inheritance
TemplateUnique name, valid includes, valid ranges, strict enforcement
BehaviorNon-empty, valid composites, valid decorators, valid subtrees
Life ArcNon-empty, unique states, valid transitions, reachable states
ScheduleValid times, valid extends chain, unique block names
Relationship>= 2 participants, valid references
SpeciesUnique name, valid includes, no cycles
EnumUnique name, unique variants, non-empty
InstitutionUnique name, valid resource links
LocationUnique name, valid field types
UseValid paths, no circular imports

Cross-References

Design Patterns

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.

Behavior Tree Patterns

Priority Fallback Chain

Use a selector to try increasingly desperate options:

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
    }
}

The tree naturally degrades: first tries the best option, then falls back through progressively worse alternatives.

Conditional Behavior Switching

Use guards at the top level to switch between behavioral modes:

behavior ModeSwitcher {
    choose mode {
        if(is_combat_mode) {
            include CombatBehavior
        }

        if(is_exploration_mode) {
            include ExplorationBehavior
        }

        if(is_social_mode) {
            include SocialBehavior
        }

        include IdleBehavior
    }
}

Composite Subtree Pattern

Break complex behaviors into focused, reusable subtrees:

// 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
    }
}

Repeating Patrol with Interrupts

Use a repeating patrol that can be interrupted by higher-priority events:

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)
        }
    }
}

The combat behavior preempts patrol when threats appear, then patrol resumes.

Character Architecture Patterns

Species + Templates Composition

Use species for identity and templates for capabilities:

// 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
}

Strict Templates for Schema Enforcement

Use strict templates when you need controlled, uniform entities:

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!
// }

Template Inheritance Chains

Build template hierarchies for progressive specialization:

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
}

Relationship Patterns

Bidirectional Perspective

Model relationships where each side sees things differently:

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
}

Power Dynamic Pattern

Model unequal power relationships explicitly:

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
}

Relationship Network

Build social graphs with multiple overlapping relationships:

// 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 }

Schedule Patterns

Base Schedule with Specializations

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 }
}

Seasonal Variation

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
    }
}

Life Arc Patterns

Progressive Development

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 }
    }
}

Emotional State Machine

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
    }
}

Project Organization Patterns

Schema / World Separation

Keep type definitions separate from instance data:

my-project/
  schema/           # Types and templates (reusable)
    core_enums.sb
    templates.sb
    beings.sb
  world/            # Instances (specific to this story)
    characters/
    behaviors/
    relationships/
    locations/

Module per Domain

Group related declarations together:

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

Anti-Patterns to Avoid

Deep nesting: More than 4-5 levels of behavior tree nesting is hard to read. Use include to flatten.

God behaviors: One massive behavior tree doing everything. Break it into focused subtrees.

Deep species hierarchies: More than 2-3 levels of species includes is rarely needed. Use templates for variation.

Duplicated logic: If two behaviors share logic, extract it into a shared subtree.

Unnamed nodes: Always label composite nodes in behavior trees for readability.

Cross-References

The SBIR Binary Format

SBIR (Storybook Intermediate Representation) is the compiled binary format produced by the Storybook compiler. It transforms human-readable .sb files into an optimized, machine-consumable format for simulation runtimes.

Compilation Pipeline

.sb files → Lexer → Parser → AST → Resolver → SBIR Binary
  1. Lexer: Tokenizes raw text into tokens
  2. Parser: Builds an Abstract Syntax Tree (AST) from tokens
  3. Resolver: Validates, resolves cross-references, merges templates, and produces SBIR

What SBIR Contains

SBIR represents the fully resolved state of a Storybook project:

  • Characters: All fields resolved (species + templates merged, overrides applied)
  • Behaviors: Behavior trees with all subtree references inlined
  • Life Arcs: State machines with validated transitions
  • Schedules: Time blocks with resolved action references
  • Relationships: Participants with resolved entity references
  • Institutions: Fully resolved field sets
  • Locations: Fully resolved field sets
  • Species: Fully resolved inheritance chains
  • Enums: Complete variant lists

Resolution Process

Template Merging

When a character uses templates, SBIR contains the fully merged result:

Source:

species Human { lifespan: 70, speed: 1.0 }
template Warrior { speed: 1.5, strength: 10 }

character Conan: Human from Warrior {
    strength: 20
}

In SBIR, Conan’s fields are:

  • lifespan: 70 (from Human)
  • speed: 1.5 (Warrior overrides Human)
  • strength: 20 (Conan overrides Warrior)

Cross-File Reference Resolution

SBIR resolves all use statements and qualified paths. A relationship referencing Martha in a different file is resolved to the concrete character definition.

Validation

Before producing SBIR, the resolver validates all constraints documented in Validation Rules:

  • All references resolve to defined declarations
  • No circular dependencies
  • Type consistency
  • Domain constraints (bond ranges, schedule validity)

Design Goals

Compact: SBIR strips comments, whitespace, and redundant structure.

Self-contained: No external references – everything is resolved and inlined.

Fast to load: Simulation runtimes can load SBIR without re-parsing or re-resolving.

Validated: If SBIR was produced, the source was valid. Runtimes do not need to re-validate.

Usage

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.

For the current SBIR specification, see the SBIR v0.2.0 Spec.

Cross-References

Integration Guide

This chapter covers how to integrate Storybook into larger systems – game engines, simulation frameworks, and custom applications.

Architecture Overview

Storybook operates in two phases:

  1. Compile time: .sb files are parsed, validated, and compiled into SBIR
  2. Runtime: A simulation engine consumes SBIR and drives character behavior
                    Compile Time                    Runtime
.sb files → [Storybook Compiler] → SBIR → [Simulation Engine] → Character Actions

The Storybook Compiler

The compiler is a Rust library and CLI tool that processes .sb files.

CLI Usage

# 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/

As a Library

The compiler can be embedded as a Rust dependency:

#![allow(unused)]
fn main() {
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);
}
}

Key Types

The resolved output provides these primary types:

TypeDescription
ResolvedFileContainer for all resolved declarations
ResolvedCharacterCharacter with merged species/template fields
ResolvedBehaviorBehavior tree with resolved subtree references
ResolvedLifeArcState machine with validated transitions
ResolvedScheduleSchedule with resolved time blocks
ResolvedRelationshipRelationship with resolved participant references
ResolvedInstitutionInstitution with resolved fields
ResolvedLocationLocation with resolved fields
ResolvedSpeciesSpecies with resolved includes chain
ResolvedEnumEnum with variant list

Runtime Integration

Behavior Tree Execution

Runtimes are responsible for:

  1. Tick-based evaluation: Call the behavior tree root each frame/tick
  2. Action execution: Interpret action nodes (e.g., MoveTo, Attack)
  3. Condition evaluation: Evaluate expression nodes against current state
  4. Decorator state: Maintain timer/counter state for stateful decorators

Life Arc Execution

  1. Track the current state for each life arc instance
  2. Evaluate transition conditions each tick
  3. Execute on-enter actions when transitioning
  4. Maintain state persistence across ticks

Schedule Execution

  1. Get the current simulation time
  2. Find the matching schedule block (considering temporal constraints and recurrences)
  3. Execute the associated behavior tree action

Relationship Queries

Provide APIs for querying the relationship graph:

  • Find all relationships for a character
  • Get bond strength between two entities
  • Query perspective fields (self/other)

LSP Integration

Storybook includes a Language Server Protocol (LSP) implementation for editor support:

  • Hover information: Documentation for keywords and declarations
  • Go to definition: Navigate to declaration sources
  • Diagnostics: Real-time error reporting
  • Completions: Context-aware suggestions

The LSP server reuses the compiler’s parser and resolver, providing the same validation as the CLI.

Editor Setup

The Storybook LSP works with any editor that supports LSP:

  • VS Code: Via the Storybook extension
  • Zed: Via the zed-storybook extension
  • Other editors: Any LSP-compatible editor

Tree-sitter Grammar

A Tree-sitter grammar is provided for syntax highlighting and structural queries:

tree-sitter-storybook/
  grammar.js          # Grammar definition
  src/                # Generated parser

The Tree-sitter grammar supports:

  • Syntax highlighting in editors
  • Structural code queries
  • Incremental parsing for large files

File Organization for Integration

Organize your project to support both authoring and runtime consumption:

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

Cross-References

Best Practices

This chapter compiles best practices for writing clear, maintainable, and effective Storybook code. These guidelines apply across all declaration types and project sizes.

Naming Conventions

Declarations

Use PascalCase for all declaration names:

character MasterBaker { }          // Good
species DomesticCat { }            // Good
behavior GuardPatrol { }           // Good

character master_baker { }         // Avoid
behavior guard-patrol { }         // Invalid (no hyphens)

Fields

Use snake_case for field names:

character Martha {
    skill_level: 0.95            // Good
    emotional_state: focused     // Good
    years_experience: 22         // Good

    skillLevel: 0.95             // Avoid (camelCase)
}

Behavior Tree Labels

Use snake_case for node labels, with descriptive names:

choose survival_instinct {       // Good
    then fight_response { }      // Good
    then flight_response { }     // Good
}

choose s1 {                      // Avoid (meaningless)
    then a { }                   // Avoid
}

Enum Variants

Use PascalCase or snake_case consistently within an enum:

// 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
}

File Organization

Separate Schema from World

Keep reusable type definitions separate from instance data:

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

One Concern per File

Group related declarations, but avoid putting unrelated things together:

// 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 { }

Explicit Imports

Prefer explicit imports over wildcards:

// 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::*;

Character Design

Use Species for Identity, Templates for Traits

// 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
}

Document with Prose Blocks

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.
    ---
}

Prefer Flat Inheritance

Avoid deep species hierarchies. Two or three levels is usually enough:

// 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 { }

Behavior Tree Design

Name Every Composite Node

// Good: self-documenting
choose daily_priority {
    then handle_emergency { }
    then do_work { }
    then relax { }
}

// Avoid: anonymous nodes
choose {
    then { }
    then { }
}

Keep Trees Shallow

Extract deep subtrees into separate behaviors:

// 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 } } } } }
    }
}

Use Decorators for Control Flow

// Good: decorator handles timing
cooldown(30s) { CastSpell }
timeout(10s) { SolvePuzzle }
retry(3) { PickLock }

// Avoid: manual timing in actions
CheckCooldownTimer
IfCooldownReady { CastSpell }

Expression Writing

Use Parentheses for Clarity

// 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

Break Complex Conditions into Multiple Transitions

// 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
}

Use is for Enum Comparisons

// 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

Schedule Design

Use Named Blocks for Override Support

// 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 }
}
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 }
}

Relationship Design

Use Roles for Clarity

// 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
}

Use Perspectives for Asymmetry

// 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
}

General Principles

  1. Readability over brevity: Storybook code should read like a narrative, not a puzzle.

  2. Explicit over implicit: Say what you mean. Use named nodes, explicit imports, and clear field names.

  3. Flat over deep: Shallow hierarchies, short behavior trees, and focused files are easier to maintain.

  4. Composition over inheritance: Prefer combining templates over building deep species hierarchies.

  5. Document with prose: Prose blocks are a feature, not clutter. Use them to explain intent alongside data.

  6. One concept per declaration: Each behavior tree, life arc, or schedule should have a single clear purpose.

Cross-References

Baker Family Complete

This example demonstrates a complete Storybook project modeling Martha’s bakery and the people around it. It showcases all major language features working together.

Project Structure

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

Schema Layer

Enums

// 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
}

Species

// 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.
    ---
}

Templates

// 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
}

Characters

Martha

// 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.
        ---
    }
}

Jane

// 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.
    ---
}

Elena

// 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.
        ---
    }
}

Gregory

// 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.
    ---
}

Behaviors

// 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
        }
    }
}

Relationships

// 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
}

Locations

// 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.
    ---
}

Institutions

// 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.
    ---
}

Key Takeaways

This example demonstrates:

  1. Layered architecture: Schema (types) separated from world (instances)
  2. Species + Templates: Human species combined with Baker and BusinessOwner templates
  3. Rich behavior trees: Morning routine and customer service with choose, then, and repeat
  4. Asymmetric relationships: Martha and Elena see their mentorship differently
  5. Life arcs: Elena’s career journey modeled as a state machine
  6. Prose everywhere: Every declaration includes narrative context

Cross-References

Day in the Life

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.

The Baker

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.
    ---
}

The Schedule

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
    }
}

Behaviors

Morning Routine

behavior BakerMorningRoutine {
    then morning_sequence {
        WakeUp
        WashFace
        DressInWorkClothes

        // Check the sourdough starter
        then check_starter {
            ExamineStarter
            if(starter_healthy) {
                FeedStarter
            }
        }

        WalkToKitchen
        LightOven
    }
}

Baking Behavior

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)
                }
            }
        }
    }
}

Customer Service

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
    }
}

Emergency Handling

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
            }
        }
    }
}

Life Arc: Career and Energy

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
    }
}

Relationships

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.
    ---
}

Key Takeaways

This example demonstrates:

  1. Schedule-driven daily flow: Precise time blocks govern Martha’s entire day
  2. Seasonal and weekly variations: Summer hours and Saturday market
  3. Layered behaviors: Emergency behavior preempts normal routine via priority
  4. Realistic action sequences: Baking modeled step by step with parameters
  5. Energy management: Life arc tracks fatigue affecting mood and output quality
  6. Social connections: Relationships with apprentice and customers add depth

Cross-References

Character Evolution

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.

The Apprentice’s Journey

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.

Schema

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
}

The Character at Different Stages

// 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.
    ---
}

The Evolution Life Arc

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.
        ---
    }
}

Evolving Behaviors

Elena’s behavior changes as she progresses:

// 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
        }
    }
}

Evolving Relationships

The mentor relationship changes as Elena grows:

// 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
}

Evolving Schedules

Elena’s schedule changes as she takes on more responsibility:

// 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
    }
}

Key Takeaways

This example demonstrates:

  1. Life arcs as character development: Elena’s career progression modeled as states
  2. Evolving behaviors: Different behavior trees for each stage of growth
  3. Changing relationships: The mentor dynamic shifts from dependency to partnership
  4. Schedule evolution: Responsibilities grow with skill level
  5. Narrative prose: Each life arc state tells a story about who Elena is becoming
  6. Template progression: Templates define the capability profile at each stage

Cross-References

Multi-Character Interactions

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.

The Setting

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.
    ---
}

The Characters

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.
    ---
}

Interlocking Behaviors

Martha’s Behavior

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
            }
        }
    }
}

Jane’s Behavior

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
            }
        }
    }
}

Elena’s Behavior

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
        }
    }
}

Gregory’s Behavior

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
        }
    }
}

Relationships

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
}

The Saturday Schedule

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
    }
}

Life Arc: Elena’s Saturday Confidence

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.
        ---
    }
}

Key Takeaways

This example demonstrates:

  1. Multiple characters with interlocking behaviors: Martha, Jane, Elena, and Gregory react to each other
  2. Character coordination: Martha and Jane operate as a seamless team
  3. Asymmetric group dynamics: Gregory is an unofficial helper, Elena is growing into her role
  4. Location as context: The busy bakery storefront defines the scene
  5. Institution modeling: The Saturday crew as a coordinated work team
  6. Visitor arc: Elena’s confidence through the Saturday rush modeled as a life arc
  7. Rich prose: Every character and relationship includes narrative perspective

Cross-References