Initial Commit
This commit is contained in:
160
openspec/changes/enterprise-gaming-behavior-tree/proposal.md
Normal file
160
openspec/changes/enterprise-gaming-behavior-tree/proposal.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user