# 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 ::= | | | | | | ::= "repeat" ? "{" "}" ::= "(" ")" | "(" ".." ")" ::= "invert" "{" "}" ::= "retry" "(" ")" "{" "}" ::= "timeout" "(" ")" "{" "}" ::= "cooldown" "(" ")" "{" "}" ::= "if" "(" ")" "{" "}" ::= ("succeed_always" | "fail_always") "{" "}" ::= ("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 ::= ::= "s" // seconds | "m" // minutes | "h" // hours | "d" // days ::= + ``` **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 `` (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