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

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

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

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

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

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

13 KiB

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:

Operator Left Type Right Type Valid?
==, != int int
==, != float float
==, != string string
==, != bool bool
==, != enum same enum
==, != int float
<, <=, >, >= int int
<, <=, >, >= float float
<, <=, >, >= string string

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