release: Storybook v0.2.0 - Major syntax and features update
BREAKING CHANGES: - Relationship syntax now requires blocks for all participants - Removed self/other perspective blocks from relationships - Replaced 'guard' keyword with 'if' for behavior tree decorators Language Features: - Add tree-sitter grammar with improved if/condition disambiguation - Add comprehensive tutorial and reference documentation - Add SBIR v0.2.0 binary format specification - Add resource linking system for behaviors and schedules - Add year-long schedule patterns (day, season, recurrence) - Add behavior tree enhancements (named nodes, decorators) Documentation: - Complete tutorial series (9 chapters) with baker family examples - Complete reference documentation for all language features - SBIR v0.2.0 specification with binary format details - Added locations and institutions documentation Examples: - Convert all examples to baker family scenario - Add comprehensive working examples Tooling: - Zed extension with LSP integration - Tree-sitter grammar for syntax highlighting - Build scripts and development tools Version Updates: - Main package: 0.1.0 → 0.2.0 - Tree-sitter grammar: 0.1.0 → 0.2.0 - Zed extension: 0.1.0 → 0.2.0 - Storybook editor: 0.1.0 → 0.2.0
This commit is contained in:
134
docs/tutorial/01-welcome.md
Normal file
134
docs/tutorial/01-welcome.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
character Martha {
|
||||
age: 34
|
||||
skill_level: 0.95
|
||||
|
||||
---description
|
||||
A master baker who runs the most popular bakery in town,
|
||||
known for her sourdough bread and apple pastries.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
character Martha { ... } // A person or creature
|
||||
behavior BakeRoutine { ... } // Decision-making logic
|
||||
relationship Family { ... } // A connection between entities
|
||||
schedule DailyRoutine { ... } // Time-based activities
|
||||
life_arc Career { ... } // How someone changes over time
|
||||
```
|
||||
|
||||
### Fields Hold Data
|
||||
|
||||
Fields use a simple `name: value` format:
|
||||
|
||||
```storybook
|
||||
age: 34 // Integer
|
||||
skill_level: 0.95 // Float
|
||||
name: "Martha Baker" // String
|
||||
is_open: true // Boolean
|
||||
wake_time: 04:30 // Time
|
||||
bake_duration: 45m // Duration
|
||||
```
|
||||
|
||||
### Prose Blocks Tell Stories
|
||||
|
||||
Prose blocks embed narrative text directly in your definitions:
|
||||
|
||||
```storybook
|
||||
---backstory
|
||||
Martha learned to bake from her grandmother, starting at age
|
||||
twelve with simple bread recipes. Over the years she mastered
|
||||
sourdough, pastries, and specialty cakes, eventually opening
|
||||
her own bakery.
|
||||
---
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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](./02-creating-characters.md) 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](../reference/09-overview.md) for precise syntax details.
|
||||
313
docs/tutorial/02-creating-characters.md
Normal file
313
docs/tutorial/02-creating-characters.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
character Martha {
|
||||
age: 34
|
||||
skill_level: 0.95
|
||||
is_open: true
|
||||
}
|
||||
```
|
||||
|
||||
Fields use the `name: value` format. Storybook supports several value types:
|
||||
|
||||
| Type | Example | Description |
|
||||
|------|---------|-------------|
|
||||
| Integer | `42` | Whole numbers |
|
||||
| Float | `0.85` | Decimal numbers |
|
||||
| String | `"hello"` | Text in double quotes |
|
||||
| Boolean | `true` / `false` | Yes or no values |
|
||||
| Time | `14:30` | Clock times |
|
||||
| Duration | `2h30m` | Time 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:
|
||||
|
||||
```storybook
|
||||
character Martha {
|
||||
age: 34
|
||||
skill_level: 0.95
|
||||
|
||||
---backstory
|
||||
Martha learned to bake from her grandmother, starting at age
|
||||
twelve with simple bread recipes. She mastered sourdough and
|
||||
pastries, eventually opening the most popular bakery in town.
|
||||
---
|
||||
|
||||
---appearance
|
||||
A confident woman in her mid-thirties, usually dusted with
|
||||
flour. Her hands are calloused from years of kneading dough.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
species Human {
|
||||
lifespan: 70
|
||||
|
||||
---description
|
||||
Bipedal mammals with complex language and tool use.
|
||||
---
|
||||
}
|
||||
|
||||
species Cat {
|
||||
lifespan: 15
|
||||
|
||||
---description
|
||||
Domestic cats make loyal companions and effective
|
||||
pest control for bakeries.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
Now use species when creating characters:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
species Human {
|
||||
lifespan: 70
|
||||
reasoning_ability: 1.0
|
||||
}
|
||||
|
||||
template CulinaryExpert {
|
||||
palate_sensitivity: 0.5..1.0
|
||||
recipes_mastered: 0..500
|
||||
can_identify_ingredients: true
|
||||
}
|
||||
|
||||
template BusinessOwner {
|
||||
business_acumen: 0.0..1.0
|
||||
manages_finances: true
|
||||
leadership: 0.0..1.0
|
||||
}
|
||||
|
||||
// A character combining species with multiple templates
|
||||
character Martha: Human from CulinaryExpert, BusinessOwner {
|
||||
age: 34
|
||||
|
||||
// From CulinaryExpert
|
||||
palate_sensitivity: 0.9
|
||||
recipes_mastered: 150
|
||||
can_identify_ingredients: true
|
||||
|
||||
// From BusinessOwner
|
||||
business_acumen: 0.8
|
||||
manages_finances: true
|
||||
leadership: 0.85
|
||||
|
||||
// Unique traits
|
||||
specialty: "sourdough"
|
||||
years_experience: 22
|
||||
|
||||
---personality
|
||||
A perfectionist in the kitchen who demands the best from her
|
||||
ingredients and her team. Warm with customers but exacting
|
||||
with her apprentices.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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`) |
|
||||
|--|--------------|-------------------|
|
||||
| **Meaning** | What the character *is* | What the character *has* |
|
||||
| **Count** | Exactly one | Zero or more |
|
||||
| **Example** | `: Human` | `from 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)
|
||||
|
||||
```storybook
|
||||
species Human {
|
||||
speed: 1.0
|
||||
}
|
||||
|
||||
template Warrior {
|
||||
speed: 1.5
|
||||
strength: 10
|
||||
}
|
||||
|
||||
template Berserker {
|
||||
speed: 2.0
|
||||
strength: 15
|
||||
}
|
||||
|
||||
character Conan: Human from Warrior, Berserker {
|
||||
strength: 20
|
||||
}
|
||||
// Resolved: speed = 2.0 (Berserker), strength = 20 (Conan)
|
||||
```
|
||||
|
||||
## Using Enums for Controlled Values
|
||||
|
||||
Enums define a fixed set of named values. They prevent typos and ensure consistency:
|
||||
|
||||
```storybook
|
||||
enum SkillLevel {
|
||||
Novice,
|
||||
Beginner,
|
||||
Intermediate,
|
||||
Advanced,
|
||||
Expert,
|
||||
Master
|
||||
}
|
||||
|
||||
enum Specialty {
|
||||
Sourdough,
|
||||
Pastries,
|
||||
Cakes,
|
||||
Bread,
|
||||
Confections
|
||||
}
|
||||
```
|
||||
|
||||
Use enum values as field values:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
// In world/characters/martha.sb
|
||||
use schema::core_enums::{SkillLevel, Specialty};
|
||||
use schema::templates::Baker;
|
||||
use schema::beings::Human;
|
||||
|
||||
character Martha: Human from Baker {
|
||||
skill_level: Master
|
||||
specialty: Sourdough
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
use schema::core_enums::{SkillLevel, Specialty};
|
||||
use schema::templates::{Baker, BusinessOwner};
|
||||
use schema::beings::Human;
|
||||
|
||||
character Martha: Human from Baker, BusinessOwner {
|
||||
// Core identity
|
||||
age: 34
|
||||
skill_level: Master
|
||||
specialty: Sourdough
|
||||
|
||||
// Professional
|
||||
years_experience: 22
|
||||
recipes_mastered: 150
|
||||
can_teach: true
|
||||
|
||||
// Business
|
||||
business_acumen: 0.8
|
||||
leadership: 0.85
|
||||
|
||||
---backstory
|
||||
Martha learned to bake from her grandmother, starting at age
|
||||
twelve with simple bread recipes. She mastered sourdough and
|
||||
pastries, eventually opening the most popular bakery in town.
|
||||
---
|
||||
|
||||
---personality
|
||||
A perfectionist in the kitchen who demands the best from her
|
||||
ingredients and her team. Warm with customers but exacting
|
||||
with her apprentices. Known for arriving at 4 AM to start
|
||||
the morning batch.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that Martha exists, let us give her something to do. In [Your First Behavior Tree](./03-first-behavior-tree.md), you will learn how characters make decisions.
|
||||
|
||||
---
|
||||
|
||||
**Reference**: For complete character syntax details, see the [Characters Reference](../reference/10-characters.md).
|
||||
203
docs/tutorial/03-first-behavior-tree.md
Normal file
203
docs/tutorial/03-first-behavior-tree.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
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.
|
||||
|
||||
```storybook
|
||||
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.
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
// Without labels (hard to read)
|
||||
choose {
|
||||
then { MixDough, BakeLoaves }
|
||||
then { ServeCustomer, CollectPayment }
|
||||
}
|
||||
|
||||
// With labels (reads like a story)
|
||||
choose priority {
|
||||
then baking { MixDough, BakeLoaves }
|
||||
then sales { ServeCustomer, CollectPayment }
|
||||
}
|
||||
```
|
||||
|
||||
## Combining choose and then
|
||||
|
||||
Behavior trees become powerful when you nest selectors and sequences:
|
||||
|
||||
```storybook
|
||||
behavior Jane_PastryRoutine {
|
||||
choose pastry_priorities {
|
||||
// Highest priority: fill custom cake orders
|
||||
then custom_orders {
|
||||
ReviewCakeOrder
|
||||
DesignDecoration
|
||||
BakeAndDecorate
|
||||
PackageForPickup
|
||||
}
|
||||
|
||||
// If no orders: prepare display pastries
|
||||
then display_pastries {
|
||||
RollPastryDough
|
||||
PrepareFillings
|
||||
AssemblePastries
|
||||
ArrangeDisplay
|
||||
}
|
||||
|
||||
// Default: experiment with new recipes
|
||||
ExperimentWithFlavors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
behavior Bakery_MorningRush {
|
||||
---description
|
||||
Handles the busy morning rush when customers are
|
||||
lining up for fresh bread and pastries.
|
||||
---
|
||||
|
||||
choose morning_priority {
|
||||
then serve_waiting_customer {
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
ThankCustomer
|
||||
}
|
||||
|
||||
then restock_display {
|
||||
CheckDisplayLevels
|
||||
FetchFromKitchen
|
||||
ArrangeOnShelves
|
||||
}
|
||||
|
||||
then quick_bake {
|
||||
CheckInventory
|
||||
StartQuickBatch
|
||||
MonitorOven
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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](./04-making-characters-act.md).
|
||||
|
||||
## Next Steps
|
||||
|
||||
Your behavior trees so far make decisions between options and run sequences of actions. In [Making Characters Act](./04-making-characters-act.md), 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](../reference/11-behavior-trees.md).
|
||||
302
docs/tutorial/04-making-characters-act.md
Normal file
302
docs/tutorial/04-making-characters-act.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
// "if" for state checks
|
||||
if(health < 20)
|
||||
|
||||
// "when" for event-like conditions
|
||||
when(alarm_triggered)
|
||||
```
|
||||
|
||||
### Condition Expressions
|
||||
|
||||
Conditions support comparisons and logical operators:
|
||||
|
||||
```storybook
|
||||
// Comparisons
|
||||
if(health < 20)
|
||||
if(distance > 100)
|
||||
if(name == "Martha")
|
||||
if(status is Curious) // 'is' is syntactic sugar for ==
|
||||
|
||||
// Logical operators
|
||||
if(hungry and tired)
|
||||
if(rich or lucky)
|
||||
if(not is_dangerous)
|
||||
|
||||
// Combined
|
||||
if(health < 50 and not has_potion)
|
||||
if((age > 18 and age < 65) or is_veteran)
|
||||
```
|
||||
|
||||
## Action Parameters
|
||||
|
||||
Actions can take named parameters using parenthesis syntax:
|
||||
|
||||
```storybook
|
||||
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
|
||||
|
||||
```storybook
|
||||
// Infinite repeat (checks oven forever)
|
||||
repeat {
|
||||
CheckOvenTemperature
|
||||
}
|
||||
|
||||
// Repeat exactly 3 times
|
||||
repeat(3) {
|
||||
KneadDough
|
||||
}
|
||||
|
||||
// Repeat between 2 and 5 times (random)
|
||||
repeat(2..5) {
|
||||
FoldDough
|
||||
}
|
||||
```
|
||||
|
||||
### invert -- Flip Results
|
||||
|
||||
Inverts success/failure. Useful for "if NOT" conditions:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
timeout(10s) {
|
||||
WaitForDoughToRise // Must finish within 10 seconds
|
||||
}
|
||||
```
|
||||
|
||||
### cooldown -- Rate Limiting
|
||||
|
||||
Prevents the child from running again within the cooldown period:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
// 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:
|
||||
|
||||
```storybook
|
||||
behavior ResilientAction {
|
||||
// Only run if oven is ready, with 20s timeout, retrying up to 3 times
|
||||
if(oven_ready) {
|
||||
timeout(20s) {
|
||||
retry(3) {
|
||||
BakeDelicateItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
behavior SourdoughRecipe {
|
||||
then sourdough {
|
||||
MixDough(recipe: "sourdough", quantity: 10)
|
||||
KneadDough(duration: 15m)
|
||||
FirstRise(duration: 2h)
|
||||
ShapeLoaves
|
||||
}
|
||||
}
|
||||
|
||||
behavior Martha_DailyRoutine {
|
||||
choose daily_priority {
|
||||
then special_orders {
|
||||
if(has_special_orders)
|
||||
include SpecialOrderBehavior
|
||||
}
|
||||
|
||||
include SourdoughRecipe // Reuse sourdough behavior
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Subtrees help you avoid duplicating behavior logic. You can also reference behaviors from other modules using qualified paths:
|
||||
|
||||
```storybook
|
||||
include behaviors::baking::sourdough
|
||||
include behaviors::service::greet_customer
|
||||
```
|
||||
|
||||
## Behavior Linking with Priorities
|
||||
|
||||
Characters can link to multiple behaviors with priorities and conditions:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
behavior MorningRush_Routine {
|
||||
---description
|
||||
The bakery's morning rush routine: serve customers, restock,
|
||||
and keep the ovens running.
|
||||
---
|
||||
|
||||
repeat {
|
||||
then rush_cycle {
|
||||
// Serve any waiting customers
|
||||
choose service_mode {
|
||||
then serve_regular {
|
||||
if(customer_waiting)
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
}
|
||||
|
||||
then restock {
|
||||
if(display_low)
|
||||
FetchFromKitchen
|
||||
ArrangeOnShelves
|
||||
}
|
||||
}
|
||||
|
||||
// Check ovens between customers
|
||||
timeout(5s) {
|
||||
CheckAllOvens
|
||||
}
|
||||
|
||||
PrepareNextBatch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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](./05-advanced-behaviors.md), you will learn patterns for building complex AI systems with nested trees, state-based switching, and modular design.
|
||||
|
||||
---
|
||||
|
||||
**Reference**: See [Decorators Reference](../reference/12-decorators.md) for all decorator types and [Expression Language](../reference/17-expressions.md) for complete condition syntax.
|
||||
222
docs/tutorial/05-advanced-behaviors.md
Normal file
222
docs/tutorial/05-advanced-behaviors.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
behavior Baker_DailyAI {
|
||||
choose daily_activity {
|
||||
// Morning: Prepare the bakery
|
||||
then morning_prep {
|
||||
if(time_is_morning)
|
||||
then prep_sequence {
|
||||
LightOven
|
||||
PrepareDough
|
||||
StartFirstBatch
|
||||
}
|
||||
}
|
||||
|
||||
// Day: Serve customers
|
||||
then day_service {
|
||||
if(time_is_daytime)
|
||||
choose service_mode {
|
||||
then serve_customer {
|
||||
if(customer_waiting)
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
}
|
||||
|
||||
then restock {
|
||||
if(display_low)
|
||||
FetchFromKitchen
|
||||
}
|
||||
|
||||
CleanCounter
|
||||
}
|
||||
}
|
||||
|
||||
// Evening: Close up
|
||||
then evening_close {
|
||||
if(time_is_evening)
|
||||
then close_sequence {
|
||||
TurnOffOvens
|
||||
CleanKitchen
|
||||
CountRegister
|
||||
LockUp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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`:
|
||||
|
||||
```storybook
|
||||
// Focused subtree: just baking
|
||||
behavior Baking_Sourdough {
|
||||
then sourdough_sequence {
|
||||
MixDough
|
||||
KneadDough
|
||||
FirstRise
|
||||
ShapeLoaves
|
||||
}
|
||||
}
|
||||
|
||||
// Focused subtree: just customer service
|
||||
behavior Service_ServeCustomer {
|
||||
then service_sequence {
|
||||
GreetCustomer
|
||||
TakeOrder
|
||||
PackageItems
|
||||
CollectPayment
|
||||
}
|
||||
}
|
||||
|
||||
// Composition: combine subtrees
|
||||
behavior Martha_FullDay {
|
||||
choose activity {
|
||||
then morning_baking {
|
||||
if(time_is_morning)
|
||||
include Baking_Sourdough
|
||||
include Baking_Pastries
|
||||
}
|
||||
|
||||
then afternoon_sales {
|
||||
if(time_is_afternoon)
|
||||
include Service_ServeCustomer
|
||||
}
|
||||
|
||||
CleanWorkstation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
behavior SmartBaker {
|
||||
choose strategy {
|
||||
// Busy mode when there are many customers
|
||||
then busy_mode {
|
||||
if(customer_count > 5 and inventory_sufficient)
|
||||
choose rush_tactics {
|
||||
ServeFastOrder
|
||||
QuickRestock
|
||||
ExpressBake
|
||||
}
|
||||
}
|
||||
|
||||
// Careful mode when supplies are low
|
||||
then careful_mode {
|
||||
if(inventory_low or special_ingredients_missing)
|
||||
choose conservation_tactics {
|
||||
ReducePortions
|
||||
SubstituteIngredients
|
||||
OrderEmergencySupply
|
||||
}
|
||||
}
|
||||
|
||||
// Normal mode otherwise
|
||||
then normal_mode {
|
||||
if(customer_count <= 5 and inventory_sufficient)
|
||||
StandardRoutine
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Decorator Combinations
|
||||
|
||||
Combine decorators to build sophisticated control patterns:
|
||||
|
||||
```storybook
|
||||
behavior Baker_SpecialRecipe {
|
||||
// Only when inventory is sufficient
|
||||
if(has_special_ingredients) {
|
||||
// Limited to once per hour
|
||||
cooldown(1h) {
|
||||
// Must complete within 30 minutes
|
||||
timeout(30m) {
|
||||
// Try up to 3 times
|
||||
retry(3) {
|
||||
then bake_special {
|
||||
PrepareSpecialDough
|
||||
BakeAtPreciseTemperature
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Prose Documentation
|
||||
|
||||
Add narrative context to complex behaviors with prose blocks:
|
||||
|
||||
```storybook
|
||||
behavior Elena_TrainingSession {
|
||||
---description
|
||||
Elena practicing a new recipe under Martha's guidance.
|
||||
Uses retry decorator for persistence and if for
|
||||
checking readiness.
|
||||
---
|
||||
|
||||
choose training_strategy {
|
||||
then practice_supervised {
|
||||
if(martha_available)
|
||||
retry(3) {
|
||||
then attempt_recipe {
|
||||
ReviewRecipeSteps
|
||||
MeasureIngredients
|
||||
MixAndKnead
|
||||
CheckWithMartha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
then practice_solo {
|
||||
if(not martha_available)
|
||||
then solo_attempt {
|
||||
ReviewRecipeNotes
|
||||
AttemptRecipeFromMemory
|
||||
TasteTestResult
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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](./06-relationships.md), you will model how characters connect to each other -- friendships, rivalries, parent-child bonds, and more.
|
||||
|
||||
---
|
||||
|
||||
**Reference**: See [Behavior Trees Reference](../reference/11-behavior-trees.md) and [Decorators Reference](../reference/12-decorators.md) for complete syntax.
|
||||
212
docs/tutorial/06-relationships.md
Normal file
212
docs/tutorial/06-relationships.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
relationship MentorApprentice {
|
||||
Martha as mentor self {
|
||||
patience: 0.8
|
||||
investment_in_student: 0.9
|
||||
} other {
|
||||
sees_potential: 0.85
|
||||
frustration_level: 0.2
|
||||
}
|
||||
|
||||
Elena as apprentice self {
|
||||
dedication: 0.9
|
||||
overwhelmed: 0.4
|
||||
} other {
|
||||
admiration: 0.95
|
||||
desire_to_impress: 0.9
|
||||
}
|
||||
|
||||
bond: 0.85
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
relationship MarthaAndGregory {
|
||||
Martha {
|
||||
role: shopkeeper
|
||||
values_loyalty: 0.9
|
||||
|
||||
---perspective
|
||||
Martha appreciates Gregory's unwavering loyalty. He has
|
||||
been buying her sourdough loaf every morning for fifteen
|
||||
years. Their brief daily exchanges about the weather and
|
||||
local gossip are a comforting routine.
|
||||
---
|
||||
}
|
||||
|
||||
Gregory {
|
||||
role: regular_customer
|
||||
always_orders: "sourdough_loaf"
|
||||
|
||||
---perspective
|
||||
Gregory considers Martha's bakery a cornerstone of his
|
||||
daily routine. The bread is excellent, but it is the brief
|
||||
human connection that keeps him coming back. He worries
|
||||
about what would happen if she ever retired.
|
||||
---
|
||||
}
|
||||
|
||||
bond: 0.7
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Party Relationships
|
||||
|
||||
Relationships can involve more than two participants:
|
||||
|
||||
```storybook
|
||||
relationship BakerFamily {
|
||||
Martha as parent
|
||||
Jane as parent
|
||||
Emma as child
|
||||
|
||||
household: "Baker Residence"
|
||||
family_bond: 0.95
|
||||
dinner_time: 18:00
|
||||
|
||||
---dynamics
|
||||
A loving family running a bakery together. Martha handles
|
||||
the bread, Jane manages pastries, and Emma helps out on
|
||||
weekends while learning the craft.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
## Asymmetric Awareness
|
||||
|
||||
Relationships can model situations where one party does not know the relationship exists:
|
||||
|
||||
```storybook
|
||||
relationship BossAndNewHire {
|
||||
Martha {
|
||||
role: boss
|
||||
aware_of_struggles: false
|
||||
expects: high_quality_work
|
||||
|
||||
---perspective
|
||||
Martha sees the new hire as competent and expects them
|
||||
to learn the bakery routines quickly. She has no idea
|
||||
they are struggling with the early morning schedule.
|
||||
---
|
||||
}
|
||||
|
||||
NewHire {
|
||||
role: employee
|
||||
intimidated: 0.8
|
||||
hides_struggles: true
|
||||
|
||||
---perspective
|
||||
The new hire is in awe of Martha's skill but terrified
|
||||
of disappointing her. They arrive thirty minutes early
|
||||
every day to practice techniques before she gets in.
|
||||
---
|
||||
}
|
||||
|
||||
bond: 0.4
|
||||
}
|
||||
```
|
||||
|
||||
## Institutional Relationships
|
||||
|
||||
Institutions can participate in relationships too:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
relationship Marriage {
|
||||
Martha as spouse
|
||||
Jane as spouse
|
||||
bond: 0.9
|
||||
}
|
||||
|
||||
relationship MentorApprentice {
|
||||
Martha as mentor
|
||||
Elena as apprentice
|
||||
bond: 0.85
|
||||
}
|
||||
|
||||
relationship RegularCustomer {
|
||||
Martha as shopkeeper
|
||||
Gregory as customer
|
||||
bond: 0.7
|
||||
}
|
||||
|
||||
relationship Colleagues {
|
||||
Martha as peer
|
||||
NeighborBaker as peer
|
||||
bond: 0.5
|
||||
competitive: true
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Characters have traits, behaviors, and relationships. In [Schedules and Time](./07-schedules.md), you will give them daily routines and time-based activities.
|
||||
|
||||
---
|
||||
|
||||
**Reference**: For complete relationship syntax, see the [Relationships Reference](../reference/15-relationships.md).
|
||||
249
docs/tutorial/07-schedules.md
Normal file
249
docs/tutorial/07-schedules.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
schedule SimpleBaker {
|
||||
block morning_prep {
|
||||
05:00 - 08:00
|
||||
action: baking::prepare_dough
|
||||
}
|
||||
|
||||
block sales {
|
||||
08:00 - 14:00
|
||||
action: baking::serve_customers
|
||||
}
|
||||
|
||||
block cleanup {
|
||||
14:00 - 15:00
|
||||
action: baking::close_shop
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
character Baker: Human {
|
||||
uses schedule: SimpleBaker
|
||||
}
|
||||
```
|
||||
|
||||
For multiple schedules:
|
||||
|
||||
```storybook
|
||||
character Innkeeper: Human {
|
||||
uses schedules: [WeekdaySchedule, WeekendSchedule]
|
||||
}
|
||||
```
|
||||
|
||||
## Temporal Constraints
|
||||
|
||||
Blocks can be restricted to specific times using temporal constraints:
|
||||
|
||||
### Season Constraints
|
||||
|
||||
```storybook
|
||||
schedule SeasonalBaker {
|
||||
block summer_hours {
|
||||
06:00 - 20:00
|
||||
action: baking::long_shift
|
||||
on season summer
|
||||
}
|
||||
|
||||
block winter_hours {
|
||||
07:00 - 18:00
|
||||
action: baking::short_shift
|
||||
on season winter
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Day of Week Constraints
|
||||
|
||||
```storybook
|
||||
schedule WeeklyPattern {
|
||||
block weekday_work {
|
||||
09:00 - 17:00
|
||||
action: work::standard
|
||||
on day monday
|
||||
}
|
||||
|
||||
block weekend_rest {
|
||||
10:00 - 16:00
|
||||
action: leisure::relax
|
||||
on day saturday
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Temporal constraint values (like `summer` or `monday`) reference enums defined in your storybook:
|
||||
|
||||
```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:
|
||||
|
||||
```storybook
|
||||
schedule MarketSchedule {
|
||||
// Regular daily hours
|
||||
block work {
|
||||
08:00 - 17:00
|
||||
action: shop::regular_sales
|
||||
}
|
||||
|
||||
// Market day every Saturday
|
||||
recurs MarketDay on day saturday {
|
||||
block setup {
|
||||
06:00 - 08:00
|
||||
action: market::setup_stall
|
||||
}
|
||||
|
||||
block busy_market {
|
||||
08:00 - 18:00
|
||||
action: market::busy_sales
|
||||
}
|
||||
|
||||
block teardown {
|
||||
18:00 - 20:00
|
||||
action: market::pack_up
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
schedule BaseShopkeeper {
|
||||
block open {
|
||||
09:00 - 17:00
|
||||
action: shop::standard_hours
|
||||
}
|
||||
}
|
||||
|
||||
schedule EarlyBaker extends BaseShopkeeper {
|
||||
block open {
|
||||
05:00 - 13:00
|
||||
action: baking::early_shift
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
schedule MasterBaker extends EarlyBaker {
|
||||
block open {
|
||||
03:00 - 11:00
|
||||
action: baking::master_work
|
||||
}
|
||||
|
||||
block teaching {
|
||||
14:00 - 16:00
|
||||
action: baking::teach_apprentice
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`MasterBaker` overrides `open` again and adds a new `teaching` block.
|
||||
|
||||
## Overnight Blocks
|
||||
|
||||
Time ranges can span midnight:
|
||||
|
||||
```storybook
|
||||
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
|
||||
|
||||
```storybook
|
||||
schedule MasterBaker_FullYear {
|
||||
// Daily base
|
||||
block prep {
|
||||
04:00 - 06:00
|
||||
action: baking::prepare
|
||||
}
|
||||
|
||||
block baking {
|
||||
06:00 - 10:00
|
||||
action: baking::bake
|
||||
}
|
||||
|
||||
block sales {
|
||||
10:00 - 16:00
|
||||
action: baking::serve
|
||||
}
|
||||
|
||||
block cleanup {
|
||||
16:00 - 17:00
|
||||
action: baking::clean
|
||||
}
|
||||
|
||||
// Summer extended hours
|
||||
block summer_sales {
|
||||
10:00 - 20:00
|
||||
action: baking::busy_summer
|
||||
on season summer
|
||||
}
|
||||
|
||||
// Weekly market
|
||||
recurs MarketDay on day saturday {
|
||||
block market_prep {
|
||||
02:00 - 04:00
|
||||
action: baking::market_prep
|
||||
}
|
||||
|
||||
block market_sales {
|
||||
08:00 - 18:00
|
||||
action: baking::market_rush
|
||||
}
|
||||
}
|
||||
|
||||
// Annual harvest festival
|
||||
recurs HarvestFestival on dates "Sep 20" .. "Sep 25" {
|
||||
block festival {
|
||||
06:00 - 23:00
|
||||
action: baking::festival_mode
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Characters now have traits, behaviors, relationships, and schedules. In [Life Arcs](./08-life-arcs.md), 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](../reference/14-schedules.md).
|
||||
271
docs/tutorial/08-life-arcs.md
Normal file
271
docs/tutorial/08-life-arcs.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# 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
|
||||
|
||||
```storybook
|
||||
life_arc BakerCareer {
|
||||
state apprentice {
|
||||
on enter {
|
||||
Baker.skill_level: 0.2
|
||||
Baker.confidence: 0.3
|
||||
}
|
||||
|
||||
on skill_level > 0.5 -> journeyman
|
||||
}
|
||||
|
||||
state journeyman {
|
||||
on enter {
|
||||
Baker.confidence: 0.6
|
||||
}
|
||||
|
||||
on skill_level > 0.8 -> master
|
||||
}
|
||||
|
||||
state master {
|
||||
on enter {
|
||||
Baker.confidence: 0.95
|
||||
Baker.can_teach: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```storybook
|
||||
state early_apprentice {
|
||||
on enter {
|
||||
Elena.skill_level: novice
|
||||
Elena.confidence: timid
|
||||
}
|
||||
|
||||
on recipes_mastered > 5 -> growing_apprentice
|
||||
on quit_apprenticeship -> former_apprentice
|
||||
|
||||
---narrative
|
||||
Elena's hands shake as she measures flour. She checks the
|
||||
recipe three times before adding each ingredient. Martha
|
||||
patiently corrects her technique.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
## Transitions
|
||||
|
||||
Transitions use expressions to decide when to change state:
|
||||
|
||||
```storybook
|
||||
// Simple conditions
|
||||
on health < 20 -> fleeing
|
||||
on quest_complete -> celebrating
|
||||
|
||||
// Boolean fields
|
||||
on is_hostile -> combat
|
||||
on not ready -> waiting
|
||||
|
||||
// Equality checks
|
||||
on status is active -> active_state
|
||||
on name is "Martha" -> found_martha
|
||||
|
||||
// Complex conditions
|
||||
on health < 50 and enemy_count > 3 -> retreat
|
||||
on (tired and hungry) or exhausted -> resting
|
||||
```
|
||||
|
||||
### Transition Priority
|
||||
|
||||
When multiple transitions could fire, the **first one in declaration order** wins:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
life_arc ElenaCareer {
|
||||
---description
|
||||
Tracks Elena's professional and personal growth as she
|
||||
progresses from nervous apprentice to confident master baker.
|
||||
---
|
||||
|
||||
state early_apprentice {
|
||||
on enter {
|
||||
Elena.skill_level: novice
|
||||
Elena.confidence: timid
|
||||
}
|
||||
|
||||
on recipes_mastered > 5 -> growing_apprentice
|
||||
|
||||
---narrative
|
||||
Elena's hands shake as she measures flour. She checks the
|
||||
recipe three times before adding each ingredient. Martha
|
||||
patiently corrects her technique.
|
||||
---
|
||||
}
|
||||
|
||||
state growing_apprentice {
|
||||
on enter {
|
||||
Elena.skill_level: beginner
|
||||
Elena.confidence: uncertain
|
||||
}
|
||||
|
||||
on recipes_mastered > 15 -> journeyman
|
||||
|
||||
---narrative
|
||||
The shaking stops. Elena can make basic breads without
|
||||
looking at the recipe. She still doubts herself but
|
||||
Martha's encouragement is taking root.
|
||||
---
|
||||
}
|
||||
|
||||
state journeyman {
|
||||
on enter {
|
||||
Elena.skill_level: intermediate
|
||||
Elena.confidence: growing
|
||||
Elena.can_work_independently: true
|
||||
}
|
||||
|
||||
on recipes_mastered > 50 -> master
|
||||
|
||||
---narrative
|
||||
Elena runs the morning shift alone while Martha handles
|
||||
special orders. Customers start asking for "Elena's rolls."
|
||||
---
|
||||
}
|
||||
|
||||
state master {
|
||||
on enter {
|
||||
Elena.skill_level: master
|
||||
Elena.confidence: commanding
|
||||
Elena.can_teach: true
|
||||
}
|
||||
|
||||
---narrative
|
||||
Master Baker Elena. She has earned it. The guild acknowledges
|
||||
her mastery, and Martha beams with pride. Elena begins
|
||||
mentoring her own apprentice.
|
||||
---
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Hub-and-Spoke
|
||||
|
||||
A central idle state with branches to specialized states:
|
||||
|
||||
```storybook
|
||||
life_arc BakerBehavior {
|
||||
state idle {
|
||||
on customer_arrived -> serving
|
||||
on order_placed -> baking
|
||||
on delivery_arrived -> receiving
|
||||
}
|
||||
|
||||
state serving { on customer_served -> idle }
|
||||
state baking { on batch_complete -> idle }
|
||||
state receiving { on delivery_processed -> idle }
|
||||
}
|
||||
```
|
||||
|
||||
### Linear Progression
|
||||
|
||||
States form a one-way sequence (quests, tutorials):
|
||||
|
||||
```storybook
|
||||
life_arc Tutorial {
|
||||
state intro { on clicked_start -> movement }
|
||||
state movement { on moved_forward -> combat }
|
||||
state combat { on defeated_enemy -> inventory }
|
||||
state inventory { on opened_inventory -> complete }
|
||||
state complete {}
|
||||
}
|
||||
```
|
||||
|
||||
### Cyclic States
|
||||
|
||||
States form a loop (day/night, seasons, mood swings):
|
||||
|
||||
```storybook
|
||||
life_arc DayNightCycle {
|
||||
state dawn { on hour >= 8 -> day }
|
||||
state day { on hour >= 18 -> dusk }
|
||||
state dusk { on hour >= 20 -> night }
|
||||
state night { on hour >= 6 -> dawn }
|
||||
}
|
||||
```
|
||||
|
||||
## 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 Guide](../reference/09-overview.md)**: Complete syntax specifications
|
||||
- **[Design Patterns](../advanced/20-patterns.md)**: Common patterns and best practices
|
||||
- **[Examples Gallery](../examples/24-baker-family-complete.md)**: Full working examples to learn from
|
||||
|
||||
---
|
||||
|
||||
**Reference**: For complete life arc syntax, see the [Life Arcs Reference](../reference/13-life-arcs.md).
|
||||
500
docs/tutorial/09-locations-institutions.md
Normal file
500
docs/tutorial/09-locations-institutions.md
Normal file
@@ -0,0 +1,500 @@
|
||||
# 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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
employees: 4
|
||||
specialty: "artisan sourdough"
|
||||
daily_output_loaves: 80..120
|
||||
open: true
|
||||
established: "2011"
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
|
||||
---description
|
||||
A warm, inviting bakery on Main Street. The smell of fresh bread
|
||||
wafts out the door every morning at dawn. Martha has run the shop
|
||||
for fifteen years, and the locals consider it the heart of the
|
||||
neighborhood.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
location BakersBakery {
|
||||
type: bakery
|
||||
|
||||
---description
|
||||
The bakery on Main Street...
|
||||
---
|
||||
|
||||
---history
|
||||
Originally a hardware store, Martha converted the space in 2011...
|
||||
---
|
||||
|
||||
---atmosphere
|
||||
Flour dust catches the light from tall windows...
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building a World with Locations
|
||||
|
||||
Locations work best when they form a coherent world. Here is the Baker family's neighborhood:
|
||||
|
||||
```storybook
|
||||
location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
owner: Martha
|
||||
|
||||
---description
|
||||
Martha's artisan bakery. The stone oven was imported from France.
|
||||
---
|
||||
}
|
||||
|
||||
location BakerHome {
|
||||
type: residence
|
||||
address: "22 Elm Lane"
|
||||
bedrooms: 4
|
||||
has_garden: true
|
||||
|
||||
---description
|
||||
The Baker family home. Martha insisted on an oversized kitchen.
|
||||
---
|
||||
}
|
||||
|
||||
location BakersGuildHall {
|
||||
type: guild_hall
|
||||
address: "7 Guild Row"
|
||||
capacity: 100
|
||||
established: "1892"
|
||||
|
||||
---description
|
||||
The historic headquarters of the Bakers Guild.
|
||||
---
|
||||
}
|
||||
|
||||
location TownSquare {
|
||||
type: public_square
|
||||
capacity: 500
|
||||
has_fountain: true
|
||||
has_market_stalls: true
|
||||
|
||||
---description
|
||||
The central gathering place. On weekends, the farmers market
|
||||
fills the square with produce stalls.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
### Modeling Hierarchy
|
||||
|
||||
Storybook does not enforce a built-in parent-child relationship for locations. Instead, you use fields to express hierarchy:
|
||||
|
||||
```storybook
|
||||
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
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
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:
|
||||
|
||||
```storybook
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
reputation: 0.85
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices },
|
||||
{ tree: NegotiateSuppliers, priority: high },
|
||||
{ tree: EmergencyMeeting, when: reputation < 0.3, priority: critical }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `when` clause uses an [expression](../reference/17-expressions.md). Here, the emergency meeting behavior only activates when reputation drops below 0.3.
|
||||
|
||||
### Institutions with Schedules
|
||||
|
||||
Institutions can also follow schedules:
|
||||
|
||||
```storybook
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
uses schedule: GuildOperatingHours
|
||||
}
|
||||
```
|
||||
|
||||
For multiple schedules:
|
||||
|
||||
```storybook
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
uses schedules: [WeekdaySchedule, WeekendSchedule]
|
||||
}
|
||||
```
|
||||
|
||||
### Prose Blocks
|
||||
|
||||
Just like locations, institutions support prose blocks:
|
||||
|
||||
```storybook
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
|
||||
---description
|
||||
The Bakers Guild has been the backbone of the town's bread trade
|
||||
since 1892. Members share recipes, arrange apprenticeships, and
|
||||
collectively negotiate flour prices.
|
||||
---
|
||||
|
||||
---charter
|
||||
Article I: All members shall maintain the highest standards.
|
||||
Article II: Apprentices must complete a three-year program.
|
||||
---
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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:
|
||||
|
||||
```storybook
|
||||
character Martha {
|
||||
age: 45
|
||||
occupation: baker
|
||||
guild: BakersGuild
|
||||
guild_role: guild_master
|
||||
guild_member_since: "2005"
|
||||
}
|
||||
|
||||
character Jane {
|
||||
age: 19
|
||||
occupation: apprentice_baker
|
||||
guild: BakersGuild
|
||||
guild_role: apprentice
|
||||
guild_member_since: "2024"
|
||||
}
|
||||
```
|
||||
|
||||
### Through Relationships
|
||||
|
||||
For richer modeling, use relationships:
|
||||
|
||||
```storybook
|
||||
relationship GuildMembership {
|
||||
Martha as guild_master { years_active: 20 }
|
||||
BakersGuild as organization { }
|
||||
bond: 0.95
|
||||
}
|
||||
|
||||
relationship Apprenticeship {
|
||||
Jane as apprentice { skills_learned: 12 }
|
||||
Martha as mentor { patience_remaining: 0.7 }
|
||||
BakersGuild as guild { }
|
||||
years_completed: 1
|
||||
}
|
||||
```
|
||||
|
||||
This approach captures richer information: roles, duration, and multi-party connections.
|
||||
|
||||
---
|
||||
|
||||
## Locations vs. Institutions
|
||||
|
||||
When should you use each?
|
||||
|
||||
| Question | Use... |
|
||||
|----------|--------|
|
||||
| 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:
|
||||
|
||||
```storybook
|
||||
// The physical building
|
||||
location BakersGuildHall {
|
||||
type: guild_hall
|
||||
address: "7 Guild Row"
|
||||
capacity: 100
|
||||
}
|
||||
|
||||
// The organization that meets there
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
location: BakersGuildHall
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices },
|
||||
{ tree: NegotiateSuppliers }
|
||||
]
|
||||
|
||||
uses schedule: GuildOperatingHours
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```storybook
|
||||
// Enums for type safety
|
||||
enum PlaceType {
|
||||
bakery, residence, guild_hall, public_square
|
||||
}
|
||||
|
||||
enum GuildRole {
|
||||
guild_master, journeyman, apprentice
|
||||
}
|
||||
|
||||
// Locations: where things happen
|
||||
location BakersBakery {
|
||||
type: bakery
|
||||
address: "14 Main Street"
|
||||
capacity: 30
|
||||
owner: Martha
|
||||
|
||||
---description
|
||||
Martha's artisan bakery on Main Street.
|
||||
---
|
||||
}
|
||||
|
||||
location BakerHome {
|
||||
type: residence
|
||||
address: "22 Elm Lane"
|
||||
bedrooms: 4
|
||||
residents: ["Martha", "David", "Jane", "Tom"]
|
||||
}
|
||||
|
||||
location BakersGuildHall {
|
||||
type: guild_hall
|
||||
address: "7 Guild Row"
|
||||
capacity: 100
|
||||
|
||||
---description
|
||||
The historic Bakers Guild headquarters, established 1892.
|
||||
---
|
||||
}
|
||||
|
||||
// Institution: the organization
|
||||
institution BakersGuild {
|
||||
type: trade_guild
|
||||
members: 50
|
||||
founded: "1892"
|
||||
reputation: 0.85
|
||||
location: BakersGuildHall
|
||||
leader: Martha
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: ManageApprentices, priority: normal },
|
||||
{ tree: NegotiateSuppliers, priority: high },
|
||||
{ tree: HostAnnualBakeOff, when: month is october }
|
||||
]
|
||||
|
||||
uses schedule: GuildOperatingHours
|
||||
|
||||
---description
|
||||
The Bakers Guild oversees apprenticeships, quality standards,
|
||||
and the annual Great Bake-Off competition.
|
||||
---
|
||||
}
|
||||
|
||||
// Institution: the business
|
||||
institution BakersBakeryBusiness {
|
||||
type: business
|
||||
owner: Martha
|
||||
employees: 4
|
||||
location: BakersBakery
|
||||
|
||||
uses behaviors: [
|
||||
{ tree: DailyBakingOps, priority: high },
|
||||
{ tree: InventoryManagement }
|
||||
]
|
||||
|
||||
uses schedule: BakeryOperatingHours
|
||||
}
|
||||
|
||||
// Characters connected to all of the above
|
||||
character Martha {
|
||||
age: 45
|
||||
occupation: baker
|
||||
workplace: BakersBakery
|
||||
home: BakerHome
|
||||
guild: BakersGuild
|
||||
guild_role: guild_master
|
||||
}
|
||||
|
||||
character Jane {
|
||||
age: 19
|
||||
occupation: apprentice_baker
|
||||
workplace: BakersBakery
|
||||
home: BakerHome
|
||||
guild: BakersGuild
|
||||
guild_role: apprentice
|
||||
}
|
||||
|
||||
// Relationships tying it all together
|
||||
relationship GuildLeadership {
|
||||
Martha as guild_master { }
|
||||
BakersGuild as guild { }
|
||||
years_in_role: 8
|
||||
}
|
||||
|
||||
relationship BakeryApprenticeship {
|
||||
Jane as apprentice { }
|
||||
Martha as mentor { }
|
||||
BakersGuild as certifying_body { }
|
||||
year: 1
|
||||
total_years: 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
- Learn about [expressions](../reference/17-expressions.md) used in conditional behavior links
|
||||
- Explore [behavior trees](./03-first-behavior-tree.md) to create the behaviors your institutions use
|
||||
- See [schedules](./07-schedules.md) to define operating hours for institutions
|
||||
- Read the full [Locations Reference](../reference/16a-locations.md) and [Institutions Reference](../reference/16b-institutions.md)
|
||||
Reference in New Issue
Block a user