161 lines
4.7 KiB
Markdown
161 lines
4.7 KiB
Markdown
# OpenSpec Change: enterprise-gaming-behavior-tree
|
|
|
|
- **Change ID**: enterprise-gaming-behavior-tree
|
|
- **Package**: `@suaveplan/behavior-tree`
|
|
- **Category**: gaming/ai
|
|
- **Target path**: `gaming/ai/behavior-tree`
|
|
- **Status**: PROPOSED
|
|
- **Date**: 2026-04-24
|
|
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/behavior-tree.md`
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
Deliver `@suaveplan/behavior-tree` on the genesis branch from scratch at enterprise quality. The package provides a behavior tree engine with composite nodes (Sequence, Selector, Parallel), decorator nodes (Inverter, Repeater, Cooldown, Timeout), leaf nodes (Action, Condition, Wait), a typed Blackboard for shared agent state, and a fluent builder API. `BehaviorTreeRunner` advances the tree one tick at a time returning `SUCCESS | FAILURE | RUNNING`. Genesis adds Zod blackboard key schemas, structured errors, OTel tick-duration histogram, injectable clock, and property tests for tree traversal invariants.
|
|
|
|
---
|
|
|
|
## Current-state audit (genesis)
|
|
|
|
Package does not exist on genesis yet — fresh build.
|
|
|
|
---
|
|
|
|
## Available workspace deps on genesis
|
|
|
|
| Package | Role |
|
|
|---|---|
|
|
| `@suaveplan/error` (`workspace:^`) | `BehaviorTreeError`; `BT.*` codes |
|
|
| `@suaveplan/types` (`workspace:^`) | `Clock` |
|
|
| `zod` (`catalog:forms`) | Blackboard key schemas |
|
|
| `@opentelemetry/api` (`catalog:observability`) | Tick duration histogram |
|
|
|
|
---
|
|
|
|
## Public API surface
|
|
|
|
```ts
|
|
type NodeStatus = 'SUCCESS' | 'FAILURE' | 'RUNNING'
|
|
|
|
interface TreeNode {
|
|
readonly name: string
|
|
tick(blackboard: Blackboard, dt: number): NodeStatus
|
|
reset(): void
|
|
}
|
|
|
|
// Composites
|
|
function sequence(name: string, children: TreeNode[]): TreeNode
|
|
function selector(name: string, children: TreeNode[]): TreeNode
|
|
function parallel(name: string, children: TreeNode[], successPolicy?: 'all' | 'any', failurePolicy?: 'all' | 'any'): TreeNode
|
|
|
|
// Decorators
|
|
function inverter(name: string, child: TreeNode): TreeNode
|
|
function repeater(name: string, child: TreeNode, times?: number): TreeNode // undefined = forever
|
|
function cooldown(name: string, child: TreeNode, cooldownSeconds: number, clock?: Clock): TreeNode
|
|
function timeout(name: string, child: TreeNode, timeoutSeconds: number, clock?: Clock): TreeNode
|
|
|
|
// Leaves
|
|
function action(name: string, fn: (bb: Blackboard, dt: number) => NodeStatus): TreeNode
|
|
function condition(name: string, fn: (bb: Blackboard) => boolean): TreeNode
|
|
function wait(name: string, durationSeconds: number, clock?: Clock): TreeNode
|
|
|
|
// Blackboard
|
|
function createBlackboard<T extends Record<string, unknown>>(initial?: Partial<T>): Blackboard<T>
|
|
|
|
interface Blackboard<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
get<K extends keyof T>(key: K): T[K] | undefined
|
|
set<K extends keyof T>(key: K, value: T[K]): void
|
|
has(key: keyof T): boolean
|
|
delete(key: keyof T): void
|
|
clear(): void
|
|
clone(): Blackboard<T>
|
|
}
|
|
|
|
// Runner
|
|
function createBehaviorTreeRunner(root: TreeNode, blackboard: Blackboard, clock?: Clock): BehaviorTreeRunner
|
|
|
|
interface BehaviorTreeRunner {
|
|
tick(dt: number): NodeStatus
|
|
reset(): void
|
|
readonly lastStatus: NodeStatus | null
|
|
readonly tickCount: number
|
|
}
|
|
|
|
// Fluent builder
|
|
function bt(name: string): BehaviorTreeBuilder
|
|
```
|
|
|
|
### Error codes
|
|
|
|
```ts
|
|
type BtErrorCode =
|
|
| 'BT_EMPTY_COMPOSITE'
|
|
| 'BT_BLACKBOARD_KEY_NOT_FOUND'
|
|
| 'BT_NODE_TICK_FAILED'
|
|
| 'BT_INVALID_POLICY'
|
|
```
|
|
|
|
---
|
|
|
|
## New additions for genesis
|
|
|
|
1. **Parallel policies** — configurable success/failure policies for parallel composite.
|
|
2. **Cooldown decorator** — injectable clock; skips subtree when cooldown active, returns `FAILURE`.
|
|
3. **Timeout decorator** — returns `FAILURE` if child runs longer than timeout.
|
|
4. **Blackboard clone** — `clone()` for subtree isolation in parallel branches.
|
|
5. **OTel** — `bt.tick_duration_ms` histogram; `bt.node_status_total` counter by status.
|
|
|
|
---
|
|
|
|
## File layout
|
|
|
|
```text
|
|
gaming/ai/behavior-tree/
|
|
package.json
|
|
tsconfig.json
|
|
vitest.config.ts
|
|
src/
|
|
index.ts
|
|
error-codes.ts
|
|
constants/index.ts
|
|
types/common.ts
|
|
types/common.md
|
|
core/
|
|
blackboard/
|
|
blackboard.ts
|
|
blackboard.md
|
|
blackboard.types.ts
|
|
blackboard.test.ts
|
|
index.ts
|
|
nodes/
|
|
composites.ts
|
|
composites.md
|
|
composites.test.ts
|
|
decorators.ts
|
|
decorators.md
|
|
decorators.test.ts
|
|
leaves.ts
|
|
leaves.md
|
|
leaves.test.ts
|
|
runner/
|
|
runner.ts
|
|
runner.md
|
|
runner.types.ts
|
|
runner.test.ts
|
|
index.ts
|
|
builder/
|
|
builder.ts
|
|
builder.md
|
|
builder.test.ts
|
|
index.ts
|
|
tests/
|
|
property.test.ts
|
|
```
|
|
|
|
---
|
|
|
|
## Impact
|
|
|
|
**Downstream**: `@suaveplan/npc` builds per-NPC behavior trees; `@suaveplan/r3f-npc` renders debug tree overlays. Prerequisites: `@suaveplan/error`, `@suaveplan/types`.
|