Files
storybook/docs/reference/13-life-arcs.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

16 KiB

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