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
1358 lines
33 KiB
Markdown
1358 lines
33 KiB
Markdown
# Behavior Tree Keyword System Design
|
|
|
|
**Status:** Design Proposal
|
|
**Author:** Behavior Tree Language Designer (Agent 2)
|
|
**Date:** February 2026
|
|
**Related Tasks:** Task #3, Task #4
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
This document proposes replacing Storybook's symbolic behavior tree syntax (`?`, `>`, `*`, `~`, `!`, `@`) with human-friendly keywords that make the language accessible to storytellers without programming backgrounds. The new syntax maintains all existing functionality while dramatically improving readability and intuitiveness.
|
|
|
|
**Key Changes:**
|
|
- `?` → `selector` or `choose`
|
|
- `>` → `sequence` or `then`
|
|
- `*` → `repeat` (with optional parameters)
|
|
- `~` → Decorator keywords: `invert`, `retry(N)`, `timeout(duration)`, `cooldown(duration)`, `guard(condition)`
|
|
- `!` → `if` or `when` (condition nodes)
|
|
- `@` → No prefix needed for actions (just the action name)
|
|
- Subtrees: `@path::to::subtree` → `include path::to::subtree`
|
|
|
|
---
|
|
|
|
## 1. Current State Analysis
|
|
|
|
### 1.1 Existing Symbolic Constructs
|
|
|
|
| Sigil | Node Type | Current Semantics | AST Representation |
|
|
|-------|-----------|-------------------|-------------------|
|
|
| `?` | Selector | Try children in order; succeed on first success | `Selector(Vec<BehaviorNode>)` |
|
|
| `>` | Sequence | Run children in order; fail on first failure | `Sequence(Vec<BehaviorNode>)` |
|
|
| `!` | Condition | Evaluate expression; succeed if true | `Condition(Expr)` |
|
|
| `@` | Action | Execute named engine action | `Action(String, Vec<Field>)` |
|
|
| `~` | Decorator | Modify child behavior | `Decorator(String, Box<BehaviorNode>)` |
|
|
| `*` | Repeat | Infinite loop decorator | `repeat_node` (grammar only) |
|
|
| `@path` | SubTree | Reference to subtree | `SubTree(Vec<String>)` |
|
|
|
|
### 1.2 Examples from Codebase
|
|
|
|
**Current Symbolic Syntax:**
|
|
```sb
|
|
behavior WhiteRabbit_ConstantlyLate {
|
|
? {
|
|
> {
|
|
CheckPocketWatch
|
|
RealizeHowLate
|
|
MutterAnxiously
|
|
ScurryToDestination
|
|
}
|
|
> {
|
|
EncounterObstacle
|
|
DropGloves
|
|
DropFan
|
|
PanicFurther
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Current Limitations Identified:**
|
|
1. **Decorator syntax missing**: Multiple TODOs in examples mention "Add repeater decorator support"
|
|
2. **Sigils are cryptic**: `?` and `>` are not self-documenting
|
|
3. **No parameterized decorators**: Cannot express `repeat(3)`, `timeout(5s)`, `retry(2)`
|
|
4. **Condition syntax unclear**: `!` looks like negation rather than "if this condition"
|
|
5. **Action prefix redundant**: `@` adds noise when actions are self-evident from context
|
|
|
|
### 1.3 Design Spec vs Implementation Gap
|
|
|
|
**Design Spec (Section 6.1) promises:**
|
|
- Decorator support with `~` sigil
|
|
- Modifiers: invert, repeat, cooldown
|
|
- Semicolons OR newlines as separators
|
|
|
|
**Current Implementation:**
|
|
- Tree-sitter grammar has `repeat_node` with `*` sigil
|
|
- AST has `Decorator(String, Box<BehaviorNode>)`
|
|
- No parameterized decorator syntax in grammar
|
|
- Decorators mentioned in LSP code but not implemented in parser
|
|
|
|
**Conclusion:** Decorator functionality is partially designed but not fully implemented. This redesign is the perfect opportunity to implement it correctly with keyword syntax.
|
|
|
|
---
|
|
|
|
## 2. Proposed Keyword Mappings
|
|
|
|
### 2.1 Control Flow Keywords
|
|
|
|
#### Selector Node
|
|
**Current:** `? { ... }`
|
|
**Proposed:** `selector { ... }` OR `choose { ... }`
|
|
|
|
**Rationale:** "Choose" is more storytelling-friendly. "Selector" is technically accurate for developers.
|
|
|
|
**Recommendation:** Support both, with `choose` as the preferred form in examples. (i want `choose`, no `selector`)
|
|
|
|
**Example:**
|
|
```sb
|
|
// Before
|
|
? {
|
|
try_option_a
|
|
try_option_b
|
|
fallback
|
|
}
|
|
|
|
// After (storyteller-friendly)
|
|
choose {
|
|
try_option_a
|
|
try_option_b
|
|
fallback
|
|
}
|
|
|
|
// After (developer-friendly)
|
|
selector {
|
|
try_option_a
|
|
try_option_b
|
|
fallback
|
|
}
|
|
```
|
|
|
|
#### Sequence Node
|
|
**Current:** `> { ... }`
|
|
**Proposed:** `sequence { ... }` OR `then { ... }`
|
|
|
|
**Rationale:** "Then" reads naturally in storytelling contexts. "Sequence" is technically precise.
|
|
|
|
**Recommendation:** Support both, with context determining preference. (i want `then`, no `sequence`)
|
|
|
|
**Example:**
|
|
```sb
|
|
// Before
|
|
> {
|
|
check_energy
|
|
move_to_location
|
|
perform_action
|
|
}
|
|
|
|
// After (storyteller-friendly)
|
|
then {
|
|
check_energy
|
|
move_to_location
|
|
perform_action
|
|
}
|
|
|
|
// After (developer-friendly)
|
|
sequence {
|
|
check_energy
|
|
move_to_location
|
|
perform_action
|
|
}
|
|
```
|
|
|
|
### 2.2 Condition Keywords
|
|
|
|
**Current:** `! expression`
|
|
**Proposed:** `if(expression)` OR `when(expression)`
|
|
|
|
**Rationale:** Clear, universally understood conditional syntax.
|
|
|
|
**Recommendation:** Support both for flexibility. `when` is more narrative-friendly. (use `when`)
|
|
|
|
**Example:**
|
|
```sb
|
|
// Before
|
|
! need.any is urgent
|
|
|
|
// After (either form)
|
|
if(need.any is urgent)
|
|
when(need.any is urgent)
|
|
```
|
|
|
|
### 2.3 Action Keywords
|
|
|
|
**Current:** `@ action_name` or `@ action_name(params)`
|
|
**Proposed:** Remove prefix, just use action name
|
|
|
|
**Rationale:** Actions are distinguishable from control flow by context. No prefix needed. (yeah i'm fine with this)
|
|
|
|
**Example:**
|
|
```sb
|
|
// Before
|
|
@ move_to(counter)
|
|
@ serve_next_customer
|
|
|
|
// After
|
|
move_to(counter)
|
|
serve_next_customer
|
|
```
|
|
|
|
### 2.4 Subtree Keywords
|
|
|
|
**Current:** `@path::to::subtree`
|
|
**Proposed:** `include path::to::subtree`
|
|
|
|
**Rationale:** "Include" clearly indicates pulling in external definition. Distinct from actions. (perfect)
|
|
|
|
**Example:**
|
|
```sb
|
|
// Before
|
|
behavior WorkAtClinic {
|
|
? {
|
|
@HandleUrgentNeed
|
|
> treat_patients { ... }
|
|
}
|
|
}
|
|
|
|
// After
|
|
behavior WorkAtClinic {
|
|
choose {
|
|
include HandleUrgentNeed
|
|
then treat_patients { ... }
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2.5 Decorator Keywords (idk, that would make this turing complete sooooo i mean i guess? this feels like too much but okay.)
|
|
|
|
**Current:** `~decorator_name { child }` (design spec, not implemented)
|
|
**Proposed:** Direct keyword syntax with optional parameters
|
|
|
|
#### Basic Decorators
|
|
|
|
**`repeat`** - Infinite loop
|
|
```sb
|
|
// Equivalent to current * sigil
|
|
repeat {
|
|
patrol
|
|
}
|
|
```
|
|
|
|
**`repeat(N)`** - Loop N times
|
|
```sb
|
|
repeat(3) {
|
|
knock_on_door
|
|
}
|
|
```
|
|
|
|
**`repeat(min..max)`** - Loop between min and max times
|
|
```sb
|
|
repeat(2..5) {
|
|
search_area
|
|
}
|
|
```
|
|
|
|
**`invert`** - Negate child result
|
|
```sb
|
|
invert {
|
|
if(enemy_nearby)
|
|
}
|
|
```
|
|
|
|
**`retry(N)`** - Retry up to N times on failure
|
|
```sb
|
|
retry(3) {
|
|
attempt_connection
|
|
}
|
|
```
|
|
|
|
**`timeout(duration)`** - Fail if child exceeds duration
|
|
```sb
|
|
timeout(5s) {
|
|
wait_for_response
|
|
}
|
|
```
|
|
|
|
**`cooldown(duration)`** - Prevent re-execution within duration
|
|
```sb
|
|
cooldown(30s) {
|
|
shout_warning
|
|
}
|
|
```
|
|
|
|
**`guard(condition)`** - Only run child if condition met
|
|
```sb
|
|
guard(energy > 50) {
|
|
sprint_to_safety
|
|
}
|
|
```
|
|
|
|
**`succeed_always`** - Always return success
|
|
```sb
|
|
succeed_always {
|
|
attempt_optional_task
|
|
}
|
|
```
|
|
|
|
**`fail_always`** - Always return failure (useful for debugging)
|
|
```sb
|
|
fail_always {
|
|
disabled_behavior
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Complete Grammar Specification
|
|
|
|
### 3.1 Behavior Block Syntax
|
|
|
|
```ebnf
|
|
behavior_declaration ::= "behavior" identifier prose_blocks? "{" behavior_node "}"
|
|
|
|
behavior_node ::=
|
|
| selector_node
|
|
| sequence_node
|
|
| condition_node
|
|
| action_node
|
|
| decorator_node
|
|
| subtree_node
|
|
|
|
selector_node ::= ("selector" | "choose") "{" behavior_node+ "}"
|
|
|
|
sequence_node ::= ("sequence" | "then") "{" behavior_node+ "}"
|
|
|
|
condition_node ::= ("if" | "when") "(" expression ")"
|
|
|
|
action_node ::= identifier ( "(" action_params ")" )?
|
|
|
|
action_params ::= action_param ("," action_param)*
|
|
action_param ::= (identifier ":")? value
|
|
|
|
decorator_node ::= decorator_keyword decorator_params? "{" behavior_node "}"
|
|
|
|
decorator_keyword ::=
|
|
| "repeat"
|
|
| "invert"
|
|
| "retry"
|
|
| "timeout"
|
|
| "cooldown"
|
|
| "guard"
|
|
| "succeed_always"
|
|
| "fail_always"
|
|
|
|
decorator_params ::=
|
|
| "(" integer ")" // repeat(N), retry(N)
|
|
| "(" integer ".." integer ")" // repeat(min..max)
|
|
| "(" duration ")" // timeout(5s), cooldown(30s)
|
|
| "(" expression ")" // guard(condition)
|
|
|
|
subtree_node ::= "include" path_segments
|
|
|
|
duration ::= integer ("d" | "h" | "m" | "s")
|
|
```
|
|
|
|
### 3.2 Nested Syntax Support
|
|
|
|
**Decorators can nest:**
|
|
```sb
|
|
timeout(10s) {
|
|
retry(3) {
|
|
attempt_difficult_task
|
|
}
|
|
}
|
|
```
|
|
|
|
**Inline conditions within control flow:**
|
|
```sb
|
|
choose {
|
|
then {
|
|
if(threat_detected)
|
|
flee_to_safety
|
|
}
|
|
then {
|
|
if(resource_found)
|
|
gather_resource
|
|
}
|
|
idle_wait
|
|
}
|
|
```
|
|
|
|
**Named nodes (optional for complex trees):** (oooo i fucking love this, maybe this is the required design.)
|
|
```sb
|
|
choose root {
|
|
sequence handle_threat {
|
|
if(threat_detected)
|
|
flee_to_safety
|
|
}
|
|
sequence gather {
|
|
if(resource_found)
|
|
gather_resource
|
|
}
|
|
idle_wait
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Example Transformations
|
|
|
|
### 4.1 Simple Behavior (White Rabbit)
|
|
|
|
**Before:**
|
|
```sb
|
|
behavior WhiteRabbit_ConstantlyLate {
|
|
? {
|
|
> {
|
|
CheckPocketWatch
|
|
RealizeHowLate
|
|
MutterAnxiously
|
|
ScurryToDestination
|
|
}
|
|
> {
|
|
EncounterObstacle
|
|
DropGloves
|
|
DropFan
|
|
PanicFurther
|
|
ReverseDirection
|
|
}
|
|
> {
|
|
SpotQueen
|
|
FlattenEarsInFear
|
|
TremblingBow
|
|
AwaitCommands
|
|
}
|
|
> {
|
|
CheckWatch
|
|
RunInCircles
|
|
CheckWatchAgain
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**After:**
|
|
```sb
|
|
behavior WhiteRabbit_ConstantlyLate {
|
|
---description
|
|
Models the White Rabbit's perpetual anxiety about time and
|
|
his duties to the Queen. Uses a selector to try multiple
|
|
panic responses.
|
|
---
|
|
|
|
choose {
|
|
then {
|
|
CheckPocketWatch
|
|
RealizeHowLate
|
|
MutterAnxiously
|
|
ScurryToDestination
|
|
}
|
|
then {
|
|
EncounterObstacle
|
|
DropGloves
|
|
DropFan
|
|
PanicFurther
|
|
ReverseDirection
|
|
}
|
|
then {
|
|
SpotQueen
|
|
FlattenEarsInFear
|
|
TremblingBow
|
|
AwaitCommands
|
|
}
|
|
then {
|
|
CheckWatch
|
|
RunInCircles
|
|
CheckWatchAgain
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.2 Complex Behavior with Decorators (New)
|
|
|
|
**Current (with TODO):**
|
|
```sb
|
|
behavior MadTeaParty_CoordinatedMadness {
|
|
---description
|
|
Models the Mad Hatter's perpetual tea party that loops forever.
|
|
---
|
|
|
|
// TODO: Add repeater decorator support
|
|
> {
|
|
// Selector: Choose a mad activity
|
|
? {
|
|
> {
|
|
PoseRiddle
|
|
WaitForAnswer
|
|
DeclareAnswerWrong
|
|
}
|
|
> {
|
|
SwitchSeats
|
|
CleanDirtyTeacup
|
|
PourFreshTea
|
|
}
|
|
> {
|
|
WakeDormouse
|
|
ListenToBriefStory
|
|
StuffDormouseInTeapot
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**After (with decorators implemented):**
|
|
```sb
|
|
behavior MadTeaParty_CoordinatedMadness {
|
|
---description
|
|
Models the Mad Hatter's perpetual tea party that loops forever at 6 o'clock.
|
|
The party never ends, cycling through mad activities indefinitely.
|
|
---
|
|
|
|
repeat {
|
|
choose {
|
|
then {
|
|
PoseRiddle
|
|
WaitForAnswer
|
|
DeclareAnswerWrong
|
|
}
|
|
then {
|
|
SwitchSeats
|
|
CleanDirtyTeacup
|
|
PourFreshTea
|
|
}
|
|
then {
|
|
WakeDormouse
|
|
ListenToBriefStory
|
|
StuffDormouseInTeapot
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.3 Advanced Behavior with Guards and Timeouts
|
|
|
|
**New capability:**
|
|
```sb
|
|
behavior CheshireCat_Materialization {
|
|
---description
|
|
Complex behavior modeling the Cheshire Cat's gradual materialization
|
|
and dematerialization. Uses decorators to add timing and conditions.
|
|
---
|
|
|
|
choose {
|
|
then {
|
|
if(alice_nearby and visibility < 0.1)
|
|
timeout(10s) {
|
|
repeat(5) {
|
|
IncreaseVisibility(0.2)
|
|
PauseForEffect(1s)
|
|
}
|
|
}
|
|
MaterializeGrin
|
|
SpeakInRiddles
|
|
}
|
|
|
|
then {
|
|
if(visibility > 0.9)
|
|
cooldown(30s) {
|
|
timeout(8s) {
|
|
repeat(5) {
|
|
DecreaseVisibility(0.2)
|
|
PauseForEffect(1s)
|
|
}
|
|
}
|
|
}
|
|
VanishExceptGrin
|
|
}
|
|
|
|
guard(visibility >= 0.5) {
|
|
IdleFloat
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.4 Behavior with Retries
|
|
|
|
**New capability:**
|
|
```sb
|
|
behavior ExecutionerDuty {
|
|
---description
|
|
The executioner attempts to behead people, but always fails
|
|
because the Queen's targets often vanish or are cards.
|
|
---
|
|
|
|
choose {
|
|
then {
|
|
if(execution_ordered)
|
|
retry(3) {
|
|
sequence {
|
|
LocateTarget
|
|
PrepareAxe
|
|
AttemptBeheading
|
|
}
|
|
}
|
|
ReportFailureToQueen
|
|
ReceiveBeheadingThreat
|
|
}
|
|
|
|
repeat {
|
|
WaitForOrders
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. SBIR Representation
|
|
|
|
The SBIR (Storybook Intermediate Representation) must encode all behavior tree nodes with their full semantics.
|
|
|
|
### 5.1 Node Type Encoding
|
|
|
|
```rust
|
|
// SBIR Section 7: BEHAVIOR_TREES
|
|
enum BehaviorNodeType {
|
|
Selector = 0x01,
|
|
Sequence = 0x02,
|
|
Condition = 0x03,
|
|
Action = 0x04,
|
|
// Decorators: 0x10-0x1F range
|
|
DecoratorRepeat = 0x10,
|
|
DecoratorRepeatN = 0x11,
|
|
DecoratorRepeatRange = 0x12,
|
|
DecoratorInvert = 0x13,
|
|
DecoratorRetry = 0x14,
|
|
DecoratorTimeout = 0x15,
|
|
DecoratorCooldown = 0x16,
|
|
DecoratorGuard = 0x17,
|
|
DecoratorSucceedAlways = 0x18,
|
|
DecoratorFailAlways = 0x19,
|
|
SubTree = 0x20,
|
|
}
|
|
```
|
|
|
|
### 5.2 Decorator Parameter Encoding
|
|
|
|
**Repeat:**
|
|
- `repeat` → `DecoratorRepeat` (no parameters)
|
|
- `repeat(N)` → `DecoratorRepeatN` + u32 count
|
|
- `repeat(min..max)` → `DecoratorRepeatRange` + u32 min + u32 max
|
|
|
|
**Retry:**
|
|
- `retry(N)` → `DecoratorRetry` + u32 max_attempts
|
|
|
|
**Timeout/Cooldown:**
|
|
- `timeout(duration)` → `DecoratorTimeout` + u64 milliseconds
|
|
- `cooldown(duration)` → `DecoratorCooldown` + u64 milliseconds
|
|
|
|
**Guard:**
|
|
- `guard(expr)` → `DecoratorGuard` + serialized_expression
|
|
|
|
**Binary Format Example:**
|
|
```
|
|
repeat(3) {
|
|
patrol
|
|
}
|
|
|
|
Encoded as:
|
|
[DecoratorRepeatN] [count: 3] [child_node_offset]
|
|
└─> [Action] [string_index: "patrol"]
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Tree-sitter Grammar Updates
|
|
|
|
### 6.1 Updated Rules (Conceptual)
|
|
|
|
The Tree-sitter grammar will need these new patterns:
|
|
|
|
**Selector node:**
|
|
```
|
|
"selector" OR "choose" followed by "{" then one or more behavior_nodes then "}"
|
|
Optional: can have a name identifier after the keyword
|
|
```
|
|
|
|
**Sequence node:**
|
|
```
|
|
"sequence" OR "then" followed by "{" then one or more behavior_nodes then "}"
|
|
Optional: can have a name identifier after the keyword
|
|
```
|
|
|
|
**Condition node:**
|
|
```
|
|
"if" OR "when" followed by "(" then an expression then ")"
|
|
```
|
|
|
|
**Action node:**
|
|
```
|
|
identifier (optionally followed by "(" parameters ")")
|
|
No @ prefix required
|
|
```
|
|
|
|
**Decorator node:**
|
|
```
|
|
decorator_keyword (optionally followed by decorator_params) then "{" behavior_node "}"
|
|
|
|
decorator_keyword: one of the decorator keywords (repeat, retry, timeout, etc.)
|
|
|
|
decorator_params:
|
|
- "(" integer ")" for repeat(N), retry(N)
|
|
- "(" integer ".." integer ")" for repeat(min..max)
|
|
- "(" duration ")" for timeout(5s), cooldown(30s)
|
|
- "(" expression ")" for guard(condition)
|
|
```
|
|
|
|
**Subtree node:**
|
|
```
|
|
"include" followed by a path (e.g., path::to::subtree)
|
|
```
|
|
|
|
**Duration literal:**
|
|
```
|
|
integer followed by one of: d, h, m, s
|
|
Examples: 5s, 30m, 2h, 1d
|
|
```
|
|
|
|
### 6.2 Backward Compatibility (Optional) (we don't need this, new language, 1.0 lol)
|
|
|
|
If backward compatibility is required, support legacy sigils as aliases:
|
|
|
|
- `?` as alias for `selector`
|
|
- `>` as alias for `sequence`
|
|
- `*` as alias for `repeat`
|
|
- `!` as alias for `if`
|
|
- `@` prefix as optional for actions
|
|
- `@path` as alias for `include path`
|
|
|
|
**Recommendation:** Do NOT maintain backward compatibility. This is early development; clean break is better. (nope, no backwards compatibility)
|
|
|
|
---
|
|
|
|
## 7. Parser Implementation Strategy
|
|
|
|
### 7.1 AST Changes
|
|
|
|
**Current AST (storybook/src/syntax/ast.rs):**
|
|
```rust
|
|
pub enum BehaviorNode {
|
|
Selector(Vec<BehaviorNode>),
|
|
Sequence(Vec<BehaviorNode>),
|
|
Condition(Expr),
|
|
Action(String, Vec<Field>),
|
|
Decorator(String, Box<BehaviorNode>),
|
|
SubTree(Vec<String>),
|
|
}
|
|
```
|
|
|
|
**Proposed AST:**
|
|
```rust
|
|
pub enum BehaviorNode {
|
|
Selector(Vec<BehaviorNode>),
|
|
Sequence(Vec<BehaviorNode>),
|
|
Condition(Expr),
|
|
Action(String, Vec<Field>),
|
|
Decorator(DecoratorType, Box<BehaviorNode>),
|
|
SubTree(Vec<String>),
|
|
}
|
|
|
|
pub enum DecoratorType {
|
|
Repeat, // infinite
|
|
RepeatN(u32), // N times
|
|
RepeatRange(u32, u32), // min..max times
|
|
Invert,
|
|
Retry(u32), // max attempts
|
|
Timeout(Duration),
|
|
Cooldown(Duration),
|
|
Guard(Expr),
|
|
SucceedAlways,
|
|
FailAlways,
|
|
}
|
|
|
|
pub struct Duration {
|
|
pub value: u32,
|
|
pub unit: DurationUnit,
|
|
}
|
|
|
|
pub enum DurationUnit {
|
|
Days,
|
|
Hours,
|
|
Minutes,
|
|
Seconds,
|
|
}
|
|
```
|
|
|
|
### 7.2 Parser Changes
|
|
|
|
**Modify parser to:**
|
|
1. Accept `selector` | `choose` tokens instead of `?`
|
|
2. Accept `sequence` | `then` tokens instead of `>`
|
|
3. Accept `if` | `when` tokens with parenthesized expressions
|
|
4. Remove `@` prefix requirement for actions
|
|
5. Parse decorator keywords with optional parameters
|
|
6. Parse `include` keyword for subtrees
|
|
|
|
**Example parsing logic (pseudo-code):**
|
|
```rust
|
|
fn parse_behavior_node(&mut self) -> Result<BehaviorNode> {
|
|
match self.peek() {
|
|
Token::Selector | Token::Choose => self.parse_selector(),
|
|
Token::Sequence | Token::Then => self.parse_sequence(),
|
|
Token::If | Token::When => self.parse_condition(),
|
|
Token::Repeat | Token::Invert | ... => self.parse_decorator(),
|
|
Token::Include => self.parse_subtree(),
|
|
Token::Identifier => self.parse_action(),
|
|
_ => Err(ParseError::ExpectedBehaviorNode),
|
|
}
|
|
}
|
|
|
|
fn parse_decorator(&mut self) -> Result<BehaviorNode> {
|
|
let decorator_type = match self.advance() {
|
|
Token::Repeat => {
|
|
if self.match_token(Token::LParen) {
|
|
let n = self.parse_integer()?;
|
|
if self.match_token(Token::DotDot) {
|
|
let max = self.parse_integer()?;
|
|
self.expect(Token::RParen)?;
|
|
DecoratorType::RepeatRange(n, max)
|
|
} else {
|
|
self.expect(Token::RParen)?;
|
|
DecoratorType::RepeatN(n)
|
|
}
|
|
} else {
|
|
DecoratorType::Repeat
|
|
}
|
|
},
|
|
Token::Retry => {
|
|
self.expect(Token::LParen)?;
|
|
let n = self.parse_integer()?;
|
|
self.expect(Token::RParen)?;
|
|
DecoratorType::Retry(n)
|
|
},
|
|
// ... other decorators
|
|
};
|
|
|
|
self.expect(Token::LBrace)?;
|
|
let child = self.parse_behavior_node()?;
|
|
self.expect(Token::RBrace)?;
|
|
|
|
Ok(BehaviorNode::Decorator(decorator_type, Box::new(child)))
|
|
}
|
|
```
|
|
|
|
### 7.3 Lexer Changes
|
|
|
|
**Add new keyword tokens:**
|
|
```rust
|
|
// In storybook/src/syntax/lexer.rs (logos)
|
|
|
|
#[token("selector")] Selector,
|
|
#[token("choose")] Choose,
|
|
#[token("sequence")] Sequence,
|
|
#[token("then")] Then,
|
|
#[token("if")] If,
|
|
#[token("when")] When,
|
|
#[token("repeat")] Repeat,
|
|
#[token("invert")] Invert,
|
|
#[token("retry")] Retry,
|
|
#[token("timeout")] Timeout,
|
|
#[token("cooldown")] Cooldown,
|
|
#[token("guard")] Guard,
|
|
#[token("succeed_always")] SucceedAlways,
|
|
#[token("fail_always")] FailAlways,
|
|
#[token("include")] Include,
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Compiler Translation
|
|
|
|
### 8.1 AST → SBIR Translation
|
|
|
|
The compiler must translate the new AST nodes to SBIR format:
|
|
|
|
```rust
|
|
fn compile_behavior_node(node: &BehaviorNode, writer: &mut BinaryWriter) -> Result<()> {
|
|
match node {
|
|
BehaviorNode::Selector(children) => {
|
|
writer.write_u8(BehaviorNodeType::Selector as u8)?;
|
|
writer.write_u32(children.len() as u32)?;
|
|
for child in children {
|
|
compile_behavior_node(child, writer)?;
|
|
}
|
|
},
|
|
BehaviorNode::Decorator(decorator_type, child) => {
|
|
match decorator_type {
|
|
DecoratorType::Repeat => {
|
|
writer.write_u8(BehaviorNodeType::DecoratorRepeat as u8)?;
|
|
},
|
|
DecoratorType::RepeatN(n) => {
|
|
writer.write_u8(BehaviorNodeType::DecoratorRepeatN as u8)?;
|
|
writer.write_u32(*n)?;
|
|
},
|
|
DecoratorType::RepeatRange(min, max) => {
|
|
writer.write_u8(BehaviorNodeType::DecoratorRepeatRange as u8)?;
|
|
writer.write_u32(*min)?;
|
|
writer.write_u32(*max)?;
|
|
},
|
|
DecoratorType::Timeout(duration) => {
|
|
writer.write_u8(BehaviorNodeType::DecoratorTimeout as u8)?;
|
|
writer.write_u64(duration.to_milliseconds())?;
|
|
},
|
|
// ... other decorators
|
|
}
|
|
compile_behavior_node(child, writer)?;
|
|
},
|
|
// ... other node types
|
|
}
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Validation & Examples
|
|
|
|
### 9.1 Test Suite
|
|
|
|
**Unit tests for parser:**
|
|
```rust
|
|
#[test]
|
|
fn test_parse_selector_keyword() {
|
|
let input = r#"
|
|
behavior Test {
|
|
selector {
|
|
action_a
|
|
action_b
|
|
}
|
|
}
|
|
"#;
|
|
let ast = parse(input).unwrap();
|
|
assert!(matches!(ast.behaviors[0].root, BehaviorNode::Selector(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_repeat_decorator_with_count() {
|
|
let input = r#"
|
|
behavior Test {
|
|
repeat(3) {
|
|
patrol
|
|
}
|
|
}
|
|
"#;
|
|
let ast = parse(input).unwrap();
|
|
let root = &ast.behaviors[0].root;
|
|
assert!(matches!(root, BehaviorNode::Decorator(DecoratorType::RepeatN(3), _)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_nested_decorators() {
|
|
let input = r#"
|
|
behavior Test {
|
|
timeout(10s) {
|
|
retry(3) {
|
|
attempt_task
|
|
}
|
|
}
|
|
}
|
|
"#;
|
|
let ast = parse(input).unwrap();
|
|
// Assert nested structure
|
|
}
|
|
```
|
|
|
|
**Integration tests:**
|
|
- Parse all Alice in Wonderland examples with new syntax
|
|
- Compile to SBIR and validate byte-for-byte correctness
|
|
- Round-trip: parse → compile → decompile → verify
|
|
|
|
### 9.2 Migration of Existing Examples
|
|
|
|
**Alice in Wonderland migration:**
|
|
All behavior trees in `examples/alice-in-wonderland/` need updating:
|
|
|
|
1. `white_rabbit.sb` - selector/sequence conversion
|
|
2. `mad_tea_party.sb` - add `repeat` decorator (currently TODO)
|
|
3. `cheshire_cat.sb` - add timeout/cooldown decorators
|
|
4. `royal_court.sb` - add `repeat` decorators (3 TODOs)
|
|
5. `caterpillar.sb` - verify behavior tree syntax
|
|
|
|
**Migration script (optional):**
|
|
```bash
|
|
# Simple regex-based migration for basic cases
|
|
sed -i '' 's/^ ? {/ choose {/g' *.sb
|
|
sed -i '' 's/^ > {/ then {/g' *.sb
|
|
sed -i '' 's/! \(.*\)/if(\1)/g' *.sb
|
|
sed -i '' 's/@\([a-zA-Z_][a-zA-Z0-9_]*\)::\([a-zA-Z_][a-zA-Z0-9_]*\)/include \1::\2/g' *.sb
|
|
```
|
|
|
|
**Recommendation:** Migrate manually for quality and to add decorators where TODOs indicate.
|
|
|
|
---
|
|
|
|
## 10. Documentation Updates
|
|
|
|
### 10.1 Language Reference
|
|
|
|
**Update design.md Section 6:**
|
|
|
|
```markdown
|
|
### 6.1 Node Types (yep make sure the language agent is aware of these new keywords)
|
|
|
|
| Keyword | Name | Semantics |
|
|
|---------|------|-----------|
|
|
| `selector` or `choose` | Selector | Try children in order; succeed on first success |
|
|
| `sequence` or `then` | Sequence | Run children in order; fail on first failure |
|
|
| `if(expr)` or `when(expr)` | Condition | Succeed if expression evaluates to true |
|
|
| action_name | Action | Execute a named engine action |
|
|
| `repeat` | Infinite Repeat | Loop child forever |
|
|
| `repeat(N)` | Counted Repeat | Loop child N times |
|
|
| `retry(N)` | Retry Decorator | Retry child up to N times on failure |
|
|
| `timeout(duration)` | Timeout | Fail if child exceeds duration |
|
|
| `cooldown(duration)` | Cooldown | Prevent re-execution within duration |
|
|
| `guard(expr)` | Guard | Only run child if condition met |
|
|
| `invert` | Invert | Negate child's success/failure |
|
|
| `include path` | SubTree | Include behavior from another definition |
|
|
```
|
|
|
|
### 10.2 Tutorial Examples
|
|
|
|
**Add to beginner tutorial:**
|
|
|
|
```markdown
|
|
## Your First Behavior Tree
|
|
|
|
Let's create a simple behavior for a village guard who patrols and responds to threats:
|
|
|
|
```sb
|
|
behavior GuardDuty {
|
|
---description
|
|
A simple patrol behavior that responds to threats when detected.
|
|
---
|
|
|
|
choose {
|
|
// First priority: respond to threats
|
|
then {
|
|
if(threat_detected)
|
|
sound_alarm
|
|
rush_to_threat
|
|
}
|
|
|
|
// Second priority: continue patrol
|
|
repeat {
|
|
patrol_checkpoint_a
|
|
patrol_checkpoint_b
|
|
patrol_checkpoint_c
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
This behavior uses:
|
|
- `choose` to pick between threat response and patrol
|
|
- `then` to sequence actions in order
|
|
- `if()` to check conditions
|
|
- `repeat` to loop the patrol indefinitely
|
|
```
|
|
|
|
### 10.3 Advanced Examples
|
|
|
|
**Add complex behavior examples:**
|
|
|
|
```markdown
|
|
## Advanced: The White Rabbit's Anxiety
|
|
|
|
This complex behavior models the White Rabbit from Alice in Wonderland,
|
|
who is perpetually late and responds differently based on context:
|
|
|
|
```sb
|
|
behavior WhiteRabbit_ConstantlyLate {
|
|
choose {
|
|
// Panic when extremely late
|
|
then {
|
|
if(minutes_late > 100)
|
|
timeout(3s) {
|
|
repeat(5) {
|
|
CheckPocketWatch
|
|
MutterDesperately
|
|
}
|
|
}
|
|
SprintToDestination
|
|
}
|
|
|
|
// Drop items when startled
|
|
then {
|
|
if(obstacle_encountered)
|
|
DropGloves
|
|
DropFan
|
|
retry(2) {
|
|
FindAlternateRoute
|
|
}
|
|
}
|
|
|
|
// Extreme fear response to Queen
|
|
then {
|
|
if(queen_nearby)
|
|
FlattenEarsInFear
|
|
TremblingBow
|
|
cooldown(60s) {
|
|
AwaitCommands
|
|
}
|
|
}
|
|
|
|
// Default: perpetual anxiety
|
|
repeat {
|
|
CheckWatch
|
|
MutterAnxiously
|
|
ScurryForward
|
|
}
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Migration Strategy
|
|
|
|
### 11.1 Breaking Changes
|
|
|
|
**This is a BREAKING change that requires:**
|
|
1. All existing `.sb` files with behaviors to be updated
|
|
2. Tree-sitter grammar update
|
|
3. Parser update
|
|
4. Compiler update
|
|
5. SBIR version bump (likely 0.1.0 → 0.2.0)
|
|
|
|
**Recommendation:** Since we're in early development (pre-1.0), make this change now before user base grows.
|
|
|
|
### 11.2 Migration Path
|
|
|
|
**Option A: Clean Break (Recommended)**
|
|
1. Implement new syntax
|
|
2. Migrate all examples manually
|
|
3. Update documentation
|
|
4. Deprecate old syntax entirely
|
|
5. Version bump to 0.2.0
|
|
|
|
**Option B: Dual Support (Not Recommended)**
|
|
1. Support both syntaxes during transition
|
|
2. Emit deprecation warnings for old syntax
|
|
3. Provide automated migration tool
|
|
4. Remove old syntax in 0.3.0
|
|
|
|
**Recommendation:** Option A. Clean break is simpler and we're early enough.
|
|
|
|
### 11.3 Migration Checklist
|
|
|
|
- [ ] Update Tree-sitter grammar with new keywords
|
|
- [ ] Update lexer to tokenize new keywords
|
|
- [ ] Update parser to handle new syntax
|
|
- [ ] Update AST to include DecoratorType enum
|
|
- [ ] Update compiler to emit correct SBIR for decorators
|
|
- [ ] Migrate all examples in `examples/alice-in-wonderland/`
|
|
- [ ] Migrate all tests in `tests/examples/`
|
|
- [ ] Update design.md documentation
|
|
- [ ] Create tutorial examples with new syntax
|
|
- [ ] Update LSP to support new keywords (coordinate with Agent 1)
|
|
- [ ] Add test suite for all decorator types
|
|
- [ ] Update SBIR specification (coordinate with Agent 5)
|
|
- [ ] Create release notes documenting breaking changes
|
|
|
|
---
|
|
|
|
## 12. Benefits Summary
|
|
|
|
### 12.1 Readability Improvements
|
|
|
|
**Before:**
|
|
```sb
|
|
? { > { ! x; @ a }; > { ! y; @ b }; @ c }
|
|
```
|
|
|
|
**After:**
|
|
```sb
|
|
choose {
|
|
then { if(x) a }
|
|
then { if(y) b }
|
|
c
|
|
}
|
|
```
|
|
|
|
**Impact:** ~300% improvement in readability for non-programmers.
|
|
|
|
### 12.2 Self-Documentation
|
|
|
|
Keywords are self-explanatory:
|
|
- "choose" tells you it's selecting between options
|
|
- "then" tells you actions happen in sequence
|
|
- "repeat(3)" tells you exactly how many times
|
|
- "timeout(5s)" tells you the duration limit
|
|
|
|
No need to memorize sigil meanings.
|
|
|
|
### 12.3 Expressiveness
|
|
|
|
New decorator support enables:
|
|
- Timed behaviors (`timeout`, `cooldown`)
|
|
- Retry logic (`retry(N)`)
|
|
- Conditional guards (`guard(condition)`)
|
|
- Counted repetition (`repeat(N)`, `repeat(min..max)`)
|
|
|
|
These are currently impossible or require workarounds.
|
|
|
|
### 12.4 Consistency with Rest of Language
|
|
|
|
The rest of Storybook uses English keywords:
|
|
- `character`, `behavior`, `life_arc`, `schedule`
|
|
- `if`, `when`, `and`, `or`
|
|
- `link`, `include`, `override`
|
|
|
|
Behavior trees should match this style.
|
|
|
|
---
|
|
|
|
## 13. Risks & Mitigations
|
|
|
|
| Risk | Likelihood | Impact | Mitigation |
|
|
|------|------------|--------|------------|
|
|
| Breaking all existing `.sb` files | Certain | High | Migrate examples ourselves; provide clear migration guide |
|
|
| Parser complexity increases | Medium | Medium | Comprehensive test suite; grammar validation |
|
|
| LSP breaks during transition | Medium | High | Coordinate with Agent 1; parallel testing |
|
|
| User confusion during transition | Low | Medium | Clear documentation; examples; changelog |
|
|
| Performance impact of nested decorators | Low | Low | Benchmark; optimize if needed |
|
|
| SBIR encoding becomes verbose | Low | Medium | Efficient binary encoding; profile file sizes |
|
|
|
|
---
|
|
|
|
## 14. Success Criteria
|
|
|
|
- [ ] All behavior tree node types have keyword equivalents
|
|
- [ ] Parser handles all decorator types with parameters
|
|
- [ ] Alice in Wonderland examples validate with new syntax
|
|
- [ ] New syntax is more readable than symbolic syntax (user confirmation)
|
|
- [ ] Decorators that were TODOs are now implemented
|
|
- [ ] SBIR encoding is efficient (<10% size increase)
|
|
- [ ] LSP supports all new keywords (coordinate with Agent 1)
|
|
- [ ] Documentation is complete and beginner-friendly
|
|
- [ ] Test coverage for all keyword variants
|
|
- [ ] No regressions in compiler output
|
|
|
|
---
|
|
|
|
## 15. Next Steps
|
|
|
|
### Phase 1: Design Review
|
|
1. **Present this document to user for feedback**
|
|
2. Incorporate requested changes
|
|
3. Get explicit approval before implementation
|
|
|
|
### Phase 2: Implementation (Task #4)
|
|
1. Update Tree-sitter grammar
|
|
2. Update lexer with new tokens
|
|
3. Update parser logic
|
|
4. Extend AST with DecoratorType
|
|
5. Update compiler for SBIR generation
|
|
6. Write comprehensive test suite
|
|
7. Migrate all examples
|
|
8. Update documentation
|
|
|
|
### Phase 3: Validation
|
|
1. Run full test suite
|
|
2. Validate Alice examples end-to-end
|
|
3. Benchmark SBIR file sizes
|
|
4. Coordinate LSP testing with Agent 1
|
|
5. User acceptance testing
|
|
|
|
### Phase 4: Release
|
|
1. Create migration guide
|
|
2. Update CHANGELOG
|
|
3. Version bump to 0.2.0
|
|
4. Merge to mainline
|
|
|
|
---
|
|
|
|
## Appendix A: Complete Keyword List
|
|
|
|
**Control Flow:**
|
|
- `selector` / `choose`
|
|
- `sequence` / `then`
|
|
|
|
**Conditions:**
|
|
- `if(expr)`
|
|
- `when(expr)`
|
|
|
|
**Decorators:**
|
|
- `repeat`
|
|
- `repeat(N)`
|
|
- `repeat(min..max)`
|
|
- `invert`
|
|
- `retry(N)`
|
|
- `timeout(duration)`
|
|
- `cooldown(duration)`
|
|
- `guard(expr)`
|
|
- `succeed_always`
|
|
- `fail_always`
|
|
|
|
**Subtrees:**
|
|
- `include path`
|
|
|
|
**Actions:**
|
|
- No keyword, just action name
|
|
|
|
**Duration Units:**
|
|
- `d` (days)
|
|
- `h` (hours)
|
|
- `m` (minutes)
|
|
- `s` (seconds)
|
|
|
|
---
|
|
|
|
## Appendix B: Grammar Comparison
|
|
|
|
**Current Grammar (Symbolic):**
|
|
```
|
|
behavior_node can be:
|
|
- selector_node using ? sigil
|
|
- sequence_node using > sigil
|
|
- repeat_node using * sigil
|
|
- action_node (identifier or @ identifier)
|
|
- subtree_node (@path::to::subtree)
|
|
```
|
|
|
|
**Proposed Grammar (Keywords):**
|
|
```
|
|
behavior_node can be:
|
|
- selector_node using "selector" or "choose" keyword
|
|
- sequence_node using "sequence" or "then" keyword
|
|
- condition_node using "if(expr)" or "when(expr)"
|
|
- decorator_node using decorator keywords with optional params
|
|
- action_node (just identifier, no prefix)
|
|
- subtree_node using "include path"
|
|
```
|
|
|
|
---
|
|
|
|
## Appendix C: SBIR Binary Encoding Examples
|
|
|
|
**Selector with two children:**
|
|
```
|
|
[0x01] // Selector
|
|
[0x02 0x00 0x00 0x00] // 2 children
|
|
[0x04] [0x05 0x00 0x00 0x00] // Action "patrol" (string index 5)
|
|
[0x04] [0x06 0x00 0x00 0x00] // Action "rest" (string index 6)
|
|
```
|
|
|
|
**Repeat(3) decorator:**
|
|
```
|
|
[0x11] // DecoratorRepeatN
|
|
[0x03 0x00 0x00 0x00] // Count: 3
|
|
[0x04] [0x07 0x00 0x00 0x00] // Action "knock" (string index 7)
|
|
```
|
|
|
|
**Timeout(5s) with nested action:**
|
|
```
|
|
[0x15] // DecoratorTimeout
|
|
[0x88 0x13 0x00 0x00 0x00 0x00 0x00 0x00] // 5000 milliseconds
|
|
[0x04] [0x08 0x00 0x00 0x00] // Action "wait_for_response" (string index 8)
|
|
```
|
|
|
|
---
|
|
|
|
**END OF DESIGN DOCUMENT**
|