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:
687
docs/reference/12-decorators.md
Normal file
687
docs/reference/12-decorators.md
Normal file
@@ -0,0 +1,687 @@
|
||||
# Decorators
|
||||
|
||||
Decorators are special nodes that wrap a single child and modify its execution behavior. They enable timing control, retry logic, conditional execution, and result inversion without modifying the child node itself.
|
||||
|
||||
## What Are Decorators?
|
||||
|
||||
Decorators sit between a parent and child node, transforming the child's behavior:
|
||||
|
||||
```
|
||||
Parent
|
||||
└─ Decorator
|
||||
└─ Child
|
||||
```
|
||||
|
||||
The decorator intercepts the child's execution, potentially:
|
||||
- Repeating it multiple times
|
||||
- Timing it out
|
||||
- Inverting its result
|
||||
- Guarding its execution
|
||||
- Forcing a specific result
|
||||
|
||||
## Decorator Types
|
||||
|
||||
Storybook provides 10 decorator types:
|
||||
|
||||
| Decorator | Purpose | Example |
|
||||
|-----------|---------|---------|
|
||||
| `repeat` | Loop infinitely | `repeat { Patrol }` |
|
||||
| `repeat(N)` | Loop N times | `repeat(3) { Check }` |
|
||||
| `repeat(min..max)` | Loop min to max times | `repeat(2..5) { Search }` |
|
||||
| `invert` | Flip success/failure | `invert { IsEnemy }` |
|
||||
| `retry(N)` | Retry on failure (max N times) | `retry(5) { Open }` |
|
||||
| `timeout(duration)` | Fail after duration | `timeout(10s) { Solve }` |
|
||||
| `cooldown(duration)` | Run at most once per duration | `cooldown(30s) { Fire }` |
|
||||
| `if(condition)` | Only run if condition true | `if(health > 50) { Attack }` |
|
||||
| `succeed_always` | Always return success | `succeed_always { Try }` |
|
||||
| `fail_always` | Always return failure | `fail_always { Test }` |
|
||||
|
||||
## Syntax
|
||||
|
||||
```bnf
|
||||
<decorator> ::= <repeat-decorator>
|
||||
| <invert-decorator>
|
||||
| <retry-decorator>
|
||||
| <timeout-decorator>
|
||||
| <cooldown-decorator>
|
||||
| <if-decorator>
|
||||
| <force-result-decorator>
|
||||
|
||||
<repeat-decorator> ::= "repeat" <repeat-spec>? "{" <behavior-node> "}"
|
||||
|
||||
<repeat-spec> ::= "(" <number> ")"
|
||||
| "(" <number> ".." <number> ")"
|
||||
|
||||
<invert-decorator> ::= "invert" "{" <behavior-node> "}"
|
||||
|
||||
<retry-decorator> ::= "retry" "(" <number> ")" "{" <behavior-node> "}"
|
||||
|
||||
<timeout-decorator> ::= "timeout" "(" <duration-literal> ")" "{" <behavior-node> "}"
|
||||
|
||||
<cooldown-decorator> ::= "cooldown" "(" <duration-literal> ")" "{" <behavior-node> "}"
|
||||
|
||||
<if-decorator> ::= "if" "(" <expression> ")" "{" <behavior-node> "}"
|
||||
|
||||
<force-result-decorator> ::= ("succeed_always" | "fail_always") "{" <behavior-node> "}"
|
||||
|
||||
<duration-literal> ::= <number> ("s" | "m" | "h" | "d")
|
||||
```
|
||||
|
||||
## Repeat Decorators
|
||||
|
||||
### Infinite Repeat: `repeat`
|
||||
|
||||
Loops the child infinitely. The child is re-executed immediately after completing (success or failure).
|
||||
|
||||
```storybook
|
||||
behavior InfinitePatrol {
|
||||
repeat {
|
||||
PatrolRoute
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
1. Run child
|
||||
2. Child completes (success or failure)
|
||||
3. Immediately run child again
|
||||
4. Go to step 2 (forever)
|
||||
|
||||
**Use cases:**
|
||||
- Perpetual patrols
|
||||
- Endless background processes
|
||||
- Continuous monitoring
|
||||
|
||||
**Warning:** Infinite loops never return to parent. Ensure they're appropriate for your use case.
|
||||
|
||||
### Repeat N Times: `repeat N`
|
||||
|
||||
Repeats the child exactly N times, then returns success.
|
||||
|
||||
```storybook
|
||||
behavior CheckThreeTimes {
|
||||
repeat(3) {
|
||||
CheckDoor
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
1. Counter = 0
|
||||
2. Run child
|
||||
3. Counter++
|
||||
4. If counter < N, go to step 2
|
||||
5. Return success
|
||||
|
||||
**Use cases:**
|
||||
- Fixed iteration counts
|
||||
- "Try three times then give up"
|
||||
- Deterministic looping
|
||||
|
||||
### Repeat Range: `repeat min..max`
|
||||
|
||||
Repeats the child between min and max times. At runtime, a specific count is selected within the range.
|
||||
|
||||
```storybook
|
||||
behavior SearchRandomly {
|
||||
repeat(2..5) {
|
||||
SearchArea
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
1. Select count C randomly from [min, max]
|
||||
2. Repeat child C times (as in `repeat N`)
|
||||
|
||||
**Use cases:**
|
||||
- Variable behavior
|
||||
- Procedural variation
|
||||
- Non-deterministic actions
|
||||
|
||||
**Validation:**
|
||||
- min ≥ 0
|
||||
- max ≥ min
|
||||
- Both must be integers
|
||||
|
||||
## Invert Decorator
|
||||
|
||||
Inverts the child's return value: success becomes failure, failure becomes success.
|
||||
|
||||
```storybook
|
||||
behavior AvoidEnemies {
|
||||
invert {
|
||||
IsEnemyNearby // Success if NOT nearby
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Truth table:**
|
||||
|
||||
| Child returns | Decorator returns |
|
||||
|---------------|------------------|
|
||||
| Success | Failure |
|
||||
| Failure | Success |
|
||||
| Running | Running (unchanged) |
|
||||
|
||||
**Use cases:**
|
||||
- Negating conditions ("if NOT X")
|
||||
- Inverting success criteria
|
||||
- Converting "found" to "not found"
|
||||
|
||||
**Example:**
|
||||
```storybook
|
||||
behavior SafeExploration {
|
||||
choose safe_actions {
|
||||
// Only explore if NOT dangerous
|
||||
then explore {
|
||||
invert { IsDangerous }
|
||||
ExploreArea
|
||||
}
|
||||
|
||||
// Default: Stay put
|
||||
Wait
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Retry Decorator
|
||||
|
||||
Retries the child up to N times on failure. Returns success if any attempt succeeds, failure if all N attempts fail.
|
||||
|
||||
```storybook
|
||||
behavior PersistentDoor {
|
||||
retry(5) {
|
||||
OpenLockedDoor
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
1. Attempts = 0
|
||||
2. Run child
|
||||
3. If child succeeds → return success
|
||||
4. If child fails:
|
||||
- Attempts++
|
||||
- If attempts < N, go to step 2
|
||||
- Else return failure
|
||||
|
||||
**Use cases:**
|
||||
- Unreliable actions (lockpicking, persuasion)
|
||||
- Network/resource operations
|
||||
- Probabilistic success
|
||||
|
||||
**Example with context:**
|
||||
```storybook
|
||||
behavior Thief_PickLock {
|
||||
then attempt_entry {
|
||||
// Try to pick lock (may fail)
|
||||
retry(3) {
|
||||
PickLock
|
||||
}
|
||||
|
||||
// If succeeded, enter
|
||||
EnterBuilding
|
||||
|
||||
// If failed after 3 tries, give up
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
- N must be ≥ 1
|
||||
|
||||
## Timeout Decorator
|
||||
|
||||
Fails the child if it doesn't complete within the specified duration.
|
||||
|
||||
```storybook
|
||||
behavior TimeLimitedPuzzle {
|
||||
timeout(30s) {
|
||||
SolvePuzzle
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
1. Start timer
|
||||
2. Run child each tick
|
||||
3. If child completes before timeout → return child's result
|
||||
4. If timer expires → return failure (interrupt child)
|
||||
|
||||
**Use cases:**
|
||||
- Time-limited actions
|
||||
- Preventing infinite loops
|
||||
- Enforcing deadlines
|
||||
|
||||
**Duration formats:**
|
||||
- `5s` - 5 seconds
|
||||
- `10m` - 10 minutes
|
||||
- `2h` - 2 hours
|
||||
- `3d` - 3 days
|
||||
|
||||
**Example:**
|
||||
```storybook
|
||||
behavior QuickDecision {
|
||||
choose timed_choice {
|
||||
// Give AI 5 seconds to find optimal move
|
||||
timeout(5s) {
|
||||
CalculateOptimalStrategy
|
||||
}
|
||||
|
||||
// Fallback: Use simple heuristic
|
||||
UseQuickHeuristic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Timer starts when decorator is first entered
|
||||
- Timer resets if decorator exits and re-enters
|
||||
- Child node should handle interruption gracefully
|
||||
|
||||
## Cooldown Decorator
|
||||
|
||||
Prevents the child from running more than once per cooldown period. Fails immediately if called within cooldown.
|
||||
|
||||
```storybook
|
||||
behavior SpecialAbility {
|
||||
cooldown(30s) {
|
||||
FireCannon
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
1. Check last execution time
|
||||
2. If (current_time - last_time) < cooldown → return failure
|
||||
3. Else:
|
||||
- Run child
|
||||
- Record current_time as last_time
|
||||
- Return child's result
|
||||
|
||||
**Use cases:**
|
||||
- Rate-limiting abilities
|
||||
- Resource cooldowns (spells, items)
|
||||
- Preventing spam
|
||||
|
||||
**Example:**
|
||||
```storybook
|
||||
behavior Mage_SpellCasting {
|
||||
choose spells {
|
||||
// Fireball: 10 second cooldown
|
||||
cooldown(10s) {
|
||||
CastFireball
|
||||
}
|
||||
|
||||
// Lightning: 5 second cooldown
|
||||
cooldown(5s) {
|
||||
CastLightning
|
||||
}
|
||||
|
||||
// Basic attack: No cooldown
|
||||
MeleeAttack
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**State management:**
|
||||
- Cooldown state persists across behavior tree ticks
|
||||
- Each cooldown decorator instance has independent state
|
||||
- Cooldown timers are per-entity (not global)
|
||||
|
||||
## If Decorator
|
||||
|
||||
Only runs the child if the condition is true. Fails immediately if condition is false.
|
||||
|
||||
```storybook
|
||||
behavior ConditionalAttack {
|
||||
if(health > 50) {
|
||||
AggressiveAttack
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
1. Evaluate condition expression
|
||||
2. If true → run child and return its result
|
||||
3. If false → return failure (do not run child)
|
||||
|
||||
**Use cases:**
|
||||
- Preconditions ("only if X")
|
||||
- Resource checks ("only if have mana")
|
||||
- Safety checks ("only if safe")
|
||||
|
||||
**Expression syntax:**
|
||||
See [Expression Language](./17-expressions.md) for complete syntax.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```storybook
|
||||
behavior GuardedActions {
|
||||
choose options {
|
||||
// Only attack if have weapon and enemy close
|
||||
if(has_weapon and distance < 10) {
|
||||
Attack
|
||||
}
|
||||
|
||||
// Only heal if health below 50%
|
||||
if(health < (max_health * 0.5)) {
|
||||
Heal
|
||||
}
|
||||
|
||||
// Only flee if outnumbered
|
||||
if(enemy_count > ally_count) {
|
||||
Flee
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Comparison with bare `if` conditions:**
|
||||
|
||||
```storybook
|
||||
// Using bare 'if' condition (checks every tick, no body)
|
||||
then approach_and_attack {
|
||||
if(enemy_nearby)
|
||||
Approach
|
||||
Attack
|
||||
}
|
||||
|
||||
// Using 'if' decorator with body (precondition check, fails fast)
|
||||
if(enemy_nearby) {
|
||||
then do_attack {
|
||||
Approach
|
||||
Attack
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `if` decorator with a body is more efficient for gating expensive subtrees.
|
||||
|
||||
## Force Result Decorators
|
||||
|
||||
### `succeed_always`
|
||||
|
||||
Always returns success, regardless of child's actual result.
|
||||
|
||||
```storybook
|
||||
behavior TryOptionalTask {
|
||||
succeed_always {
|
||||
AttemptBonus // Even if fails, we don't care
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Optional tasks that shouldn't block progress
|
||||
- Logging/monitoring actions
|
||||
- Best-effort operations
|
||||
|
||||
**Example:**
|
||||
```storybook
|
||||
behavior QuestSequence {
|
||||
then main_quest {
|
||||
TalkToNPC
|
||||
|
||||
// Try to find secret, but don't fail quest if not found
|
||||
succeed_always {
|
||||
SearchForSecretDoor
|
||||
}
|
||||
|
||||
ReturnToQuestGiver
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `fail_always`
|
||||
|
||||
Always returns failure, regardless of child's actual result.
|
||||
|
||||
```storybook
|
||||
behavior TestFailure {
|
||||
fail_always {
|
||||
AlwaysSucceedsAction // But we force it to fail
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Testing/debugging
|
||||
- Forcing alternative paths
|
||||
- Disabling branches temporarily
|
||||
|
||||
**Example:**
|
||||
```storybook
|
||||
behavior UnderConstruction {
|
||||
choose abilities {
|
||||
// Temporarily disabled feature
|
||||
fail_always {
|
||||
NewExperimentalAbility
|
||||
}
|
||||
|
||||
// Fallback to old ability
|
||||
ClassicAbility
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Combining Decorators
|
||||
|
||||
Decorators can nest to create complex behaviors:
|
||||
|
||||
```storybook
|
||||
behavior ComplexPattern {
|
||||
// Repeat 3 times, each with 10 second timeout
|
||||
repeat(3) {
|
||||
timeout(10s) {
|
||||
SolveSubproblem
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**More complex nesting:**
|
||||
```storybook
|
||||
behavior ResilientAction {
|
||||
// If: Only if health > 30
|
||||
if(health > 30) {
|
||||
// Timeout: Must complete in 20 seconds
|
||||
timeout(20s) {
|
||||
// Retry: Try up to 5 times
|
||||
retry(5) {
|
||||
// Cooldown: Can only run once per minute
|
||||
cooldown(1m) {
|
||||
PerformComplexAction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution order:** Outside → Inside
|
||||
1. If checks condition
|
||||
2. Timeout starts timer
|
||||
3. Retry begins first attempt
|
||||
4. Cooldown checks last execution time
|
||||
5. Child action runs
|
||||
|
||||
## Duration Syntax
|
||||
|
||||
Timeout and cooldown decorators use duration literals:
|
||||
|
||||
```bnf
|
||||
<duration-literal> ::= <number> <unit>
|
||||
|
||||
<unit> ::= "s" // seconds
|
||||
| "m" // minutes
|
||||
| "h" // hours
|
||||
| "d" // days
|
||||
|
||||
<number> ::= <digit>+
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
- `5s` - 5 seconds
|
||||
- `30s` - 30 seconds
|
||||
- `2m` - 2 minutes
|
||||
- `10m` - 10 minutes
|
||||
- `1h` - 1 hour
|
||||
- `24h` - 24 hours
|
||||
- `7d` - 7 days
|
||||
|
||||
**Validation:**
|
||||
- Number must be positive integer
|
||||
- No compound durations (use `120s` not `2m` if runtime expects seconds)
|
||||
- No fractional units (`1.5m` not allowed; use `90s`)
|
||||
|
||||
## Comparison Table
|
||||
|
||||
| Decorator | Affects Success | Affects Failure | Affects Running | Stateful |
|
||||
|-----------|----------------|----------------|----------------|----------|
|
||||
| `repeat` | Repeat | Repeat | Wait | Yes (counter) |
|
||||
| `repeat N` | Repeat | Repeat | Wait | Yes (counter) |
|
||||
| `repeat min..max` | Repeat | Repeat | Wait | Yes (counter) |
|
||||
| `invert` | → Failure | → Success | Unchanged | No |
|
||||
| `retry N` | → Success | Retry or fail | Wait | Yes (attempts) |
|
||||
| `timeout dur` | → Success | → Success | → Failure if expired | Yes (timer) |
|
||||
| `cooldown dur` | → Success | → Success | → Success | Yes (last time) |
|
||||
| `if(expr)` | → Success | → Success | → Success | No |
|
||||
| `succeed_always` | → Success | → Success | → Success | No |
|
||||
| `fail_always` | → Failure | → Failure | → Failure | No |
|
||||
|
||||
**Stateful decorators** maintain state across ticks. **Stateless decorators** evaluate fresh every tick.
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. **Child required**: All decorators must have exactly one child node
|
||||
2. **Repeat count**: `repeat N` requires N ≥ 0
|
||||
3. **Repeat range**: `repeat min..max` requires 0 ≤ min ≤ max
|
||||
4. **Retry count**: `retry N` requires N ≥ 1
|
||||
5. **Duration positive**: Timeout/cooldown durations must be > 0
|
||||
6. **Duration format**: Must match `<number><unit>` (e.g., `10s`, `5m`)
|
||||
7. **Guard expression**: Guard condition must be valid expression
|
||||
8. **No empty decorators**: `repeat { }` is invalid (missing child)
|
||||
|
||||
## Use Cases by Category
|
||||
|
||||
### Timing Control
|
||||
- **timeout**: Prevent infinite loops, enforce time limits
|
||||
- **cooldown**: Rate-limit abilities, prevent spam
|
||||
- **repeat**: Continuous processes, patrols
|
||||
|
||||
### Reliability
|
||||
- **retry**: Handle unreliable actions, probabilistic success
|
||||
- **if**: Precondition checks, resource validation
|
||||
- **succeed_always**: Optional tasks, best-effort
|
||||
|
||||
### Logic Control
|
||||
- **invert**: Negate conditions, flip results
|
||||
- **fail_always**: Disable branches, testing
|
||||
|
||||
### Iteration
|
||||
- **repeat N**: Fixed loops, deterministic behavior
|
||||
- **repeat min..max**: Variable loops, procedural variation
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Guards for Expensive Checks
|
||||
|
||||
**Avoid:**
|
||||
```storybook
|
||||
then expensive {
|
||||
if(complex_condition)
|
||||
ExpensiveOperation
|
||||
}
|
||||
```
|
||||
|
||||
**Prefer:**
|
||||
```storybook
|
||||
if(complex_condition) {
|
||||
ExpensiveOperation // Only runs if condition passes
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Combine Timeout with Retry
|
||||
|
||||
**Avoid:** Infinite retry loops
|
||||
|
||||
**Prefer:**
|
||||
```storybook
|
||||
timeout(30s) {
|
||||
retry(5) {
|
||||
UnreliableAction
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Cooldowns for Rate Limiting
|
||||
|
||||
**Avoid:** Manual timing in actions
|
||||
|
||||
**Prefer:**
|
||||
```storybook
|
||||
cooldown(10s) {
|
||||
FireCannon
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Invert for Readable Conditions
|
||||
|
||||
**Avoid:**
|
||||
```storybook
|
||||
choose options {
|
||||
then branch_a {
|
||||
if(not is_dangerous)
|
||||
Explore
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Prefer:**
|
||||
```storybook
|
||||
choose options {
|
||||
then branch_a {
|
||||
invert { IsDangerous }
|
||||
Explore
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. succeed_always for Optional Tasks
|
||||
|
||||
**Avoid:**
|
||||
```storybook
|
||||
then quest {
|
||||
MainTask
|
||||
choose optional {
|
||||
BonusTask
|
||||
DoNothing // Awkward fallback
|
||||
}
|
||||
NextTask
|
||||
}
|
||||
```
|
||||
|
||||
**Prefer:**
|
||||
```storybook
|
||||
then quest {
|
||||
MainTask
|
||||
succeed_always { BonusTask } // Try bonus, don't fail quest
|
||||
NextTask
|
||||
}
|
||||
```
|
||||
|
||||
## Cross-References
|
||||
|
||||
- [Behavior Trees](./11-behavior-trees.md) - Using decorators in behavior trees
|
||||
- [Expression Language](./17-expressions.md) - Guard condition syntax
|
||||
- [Value Types](./18-value-types.md) - Duration literals
|
||||
- [Design Patterns](../advanced/20-patterns.md) - Common decorator patterns
|
||||
|
||||
## Related Concepts
|
||||
|
||||
- **Composability**: Decorators can nest for complex control flow
|
||||
- **Separation of concerns**: Decorators handle control flow, children handle logic
|
||||
- **State management**: Stateful decorators (repeat, retry, timeout, cooldown) persist across ticks
|
||||
- **Performance**: Guards prevent unnecessary child execution
|
||||
Reference in New Issue
Block a user