Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
13eed7b
refactor
vantreeseba Jun 5, 2024
a0c1c21
Merge remote-tracking branch 'origin/main' into refactor
vantreeseba Jun 5, 2024
82448f1
docs: add AGENTS.md, CLAUDE.md, and .agents/ documentation
vantreeseba May 13, 2026
35d7e9d
feat: implement LeafNode as abstract base class for user-defined leaf…
vantreeseba May 14, 2026
788b1f7
fix: remove SucceederNode double return, Action placeholder docs, and…
vantreeseba May 14, 2026
fb4c14a
refactor: change Blackboard to store Float values; add BlackboardTest…
vantreeseba May 14, 2026
eeba560
test: add TaskBank, TaskNode, and UtilityAgent edge case test coverage
vantreeseba May 14, 2026
dd1ef99
test: add FSM any-transition, toDot, null-guard, and GOAP multi-step …
vantreeseba May 14, 2026
c12a5a1
Merge worktree: B3 implement LeafNode
vantreeseba May 14, 2026
986e651
Merge worktree: R2+T1 Blackboard Float refactor + tests
vantreeseba May 14, 2026
75b2ce7
Merge worktree: T4+T5+R3 FSM/GOAP tests + null guard
vantreeseba May 14, 2026
a05e3b8
chore: bump haxe version to 4.3.7, minor formatting and null-coalesci…
vantreeseba May 14, 2026
df6c354
docs: update todos.md with round-2 bugs from worktree agents
vantreeseba May 14, 2026
e1f966c
fix: guard toDot() against null currentState and skip any-node when u…
vantreeseba May 14, 2026
ab52716
fix: add Plan.length property; fix PlannerTests chained-action assert…
vantreeseba May 14, 2026
deb06ad
fix: correct undefined TestState1/2 references and lambda arity in FS…
vantreeseba May 14, 2026
a676655
Merge worktree: B8 fix toDot() null crash + spurious any-node
vantreeseba May 14, 2026
42a8bcb
Merge worktree: B7+R4 Plan.length property + Planner chained-action test
vantreeseba May 14, 2026
e76811a
Merge worktree: B6+B7+R4+B8 — fix FSM/Planner tests and Plan.length
vantreeseba May 14, 2026
c2e3d48
test: expand GOAP test coverage — ActionTests cleanup, PlanTests mult…
vantreeseba May 14, 2026
ad36d67
feat: rewrite Planner with Graph+Dijkstra, iterative Plan.isCompleted…
vantreeseba May 14, 2026
37cf302
Merge worktree: GOAP source — Planner Graph+Dijkstra, Plan iterative,…
vantreeseba May 14, 2026
0e3cb6e
Merge worktree: GOAP test expansion
vantreeseba May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .agents/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Architecture — dropecho.ai

## Source Layout

```
src/dropecho/ai/
├── Blackboard.hx # Shared key→Int context passed through BT
├── TaskBank.hx # Static task registry (String → Blackboard → NODE_STATUS)
├── bt/
│ ├── BehaviorTree.hx # Root BT node; holds Blackboard, delegates to child
│ └── node/
│ ├── Node.hx # Node interface + IExecutable<T> interface
│ ├── NODE_STATUS.hx # Enum abstract: SUCCESS=0, FAILURE=1, RUNNING=2
│ ├── LeafNode.hx # Stub (commented out — not implemented)
│ ├── TaskNode.hx # Leaf node that calls a named TaskBank task
│ ├── composite/
│ │ ├── CompositeNode.hx # Base: holds Array<Node>, CurrentIterator
│ │ ├── SelectorNode.hx # OR logic — first success wins
│ │ └── SequenceNode.hx # AND logic — first failure stops
│ └── decorator/
│ ├── DecoratorNode.hx # Base: wraps single child Node
│ ├── InverterNode.hx # Flips SUCCESS↔FAILURE
│ ├── RepeaterNode.hx # Always returns RUNNING
│ ├── RepeatUntilNode.hx # Runs until child FAILURE, returns SUCCESS
│ └── SucceederNode.hx # Always returns SUCCESS (unless RUNNING)
├── fsm/
│ ├── FSM.hx # State machine: tick(), transitions, toDot()
│ └── IState.hx # State interface: getName, onEnter, onExit, tick
├── goap/
│ ├── Action.hx # Action: cost, pre/postconditions, update function
│ ├── Plan.hx # Ordered Queue<Action>; ticks and dequeues on completion
│ ├── Planner.hx # Backward-chaining plan generator
│ └── State.hx # Goal state: preconditions + relevance
└── utility/
├── Motive.hx # name + Float value; lowest = most important
└── (UtilityAgent in Motive.hx) # holds motives, finds getMostImportantMotive()

src/dropecho/util/
├── CurrentIterator.hx # Array iterator with current()/reset() — used by CompositeNode
└── NotImplementedException.hx # Thrown by base nodes (CompositeNode, DecoratorNode)
```

## Core Class Responsibilities

### `Blackboard` (`src/dropecho/ai/Blackboard.hx`)
Shared context passed to every BT node on `init()`. Stores integer facts by string key. Provides `get`, `set`, `increment`, `decrement`. Uses `AbstractMap<String, Int>` for cross-platform compatibility.

### `TaskBank` (`src/dropecho/ai/TaskBank.hx`)
Static registry mapping task names (strings) to `Task` functions (`Blackboard → NODE_STATUS`). Users register tasks with `TaskBank.register(name, fn)` before building a tree. `TaskNode` looks up and calls tasks by name at execute time.

### `BehaviorTree` (`src/dropecho/ai/bt/BehaviorTree.hx`)
The user-facing entry point for behavior trees. Wraps a single root `Node`. `init(context)` propagates the `Blackboard` down the tree. `execute()` delegates to the root node and returns its status.

### `CompositeNode` (`src/dropecho/ai/bt/node/composite/CompositeNode.hx`)
Base for multi-child nodes. Stores `Array<Node>` and a `CurrentIterator<Node>` for position tracking. `init()` propagates context to all children. Subclasses implement `execute()` with their own selection logic.

### `SelectorNode`
Tries children in order using `childIterator`. On SUCCESS: resets iterator, returns SUCCESS. On FAILURE: advances to next child; if no more children, resets and returns FAILURE. Otherwise returns RUNNING.

### `SequenceNode`
Runs children in order. On RUNNING: stays. On FAILURE or all-SUCCESS (no more children): resets iterator and returns status. Otherwise advances to next and returns RUNNING.

### `DecoratorNode` (`src/dropecho/ai/bt/node/decorator/DecoratorNode.hx`)
Base for single-child wrapper nodes. Propagates `init()` to the child. Subclasses override `execute()`.

### `FSM` (`src/dropecho/ai/fsm/FSM.hx`)
Holds the current `IState` and two transition tables: per-state transitions (`_transitions: AbstractMap<String, Array<Transition>>`) and global transitions (`_anyTransitions: Array<Transition>`). `tick()` runs current state then checks for a matching transition (any-transitions checked first). On match, calls `onExit()` on current and `onEnter()` on new state.

### `Planner` (`src/dropecho/ai/goap/Planner.hx`)
Backward-chaining planner. Given a `State` goal and `Array<Action>` sorted by cost, iterates goal preconditions and finds the cheapest action whose postconditions satisfy each. Recursively adds that action's preconditions to the worklist. Returns a `Plan` or `null` if unsatisfiable.

### `Plan` (`src/dropecho/ai/goap/Plan.hx`)
Wraps a `Queue<Action>`. `update(dT)` ticks the front action. `isCompleted()` recursively dequeues actions whose `postconditions_satisfied()` returns true; returns `true` when the queue is empty.

## Behavior Tree Execution Flow

```
user: tree.init(blackboard)
→ BehaviorTree.init(context)
→ child.init(context) # recurses through composite/decorator chains
→ TaskNode.init(context) # stores context for use in execute()

user: tree.execute() (called every game tick)
→ BehaviorTree.execute()
→ child.execute()
→ CompositeNode / DecoratorNode.execute()
→ childIterator.current().execute() # or child.execute() for decorators
→ TaskNode.execute()
→ TaskBank.get(name)(context) # user-defined task function
→ returns NODE_STATUS
```

## FSM Execution Flow

```
user: fsm.tick()
→ _currentState.tick() # run current state logic
→ getTransition() # check _anyTransitions first, then _transitions[currentName]
→ if transition found:
_currentState.onExit()
transition.to.onEnter()
_currentState = transition.to
```

## GOAP Execution Flow

```
var planner = new Planner(goalState, availableActions)
var plan = planner.generatePlan()
→ start with goal.Preconditions as worklist
→ while worklist not empty:
findBestMatch(precondition) → lowest-cost Action whose Postconditions contain precondition
plan.unshift(action)
prepend action.Preconditions to worklist
→ return Plan(orderedActions)

// per game tick:
var done = plan.update(deltaTime)
→ peek front action, call action.update(dT)
→ isCompleted() dequeues actions whose postconditions are satisfied
```

## Key Design Patterns

1. **`NODE_STATUS` over booleans** — all BT nodes return a three-value status; RUNNING enables multi-frame behaviors
2. **`CurrentIterator` for resumable composite traversal** — stores position across ticks without re-scanning from index 0
3. **`TaskBank` as indirection layer** — decouples tree structure from behavior implementation; tasks registered globally
4. **`AbstractMap` / `AbstractFunc` everywhere** — JS callers pass plain objects, C# callers pass native types
5. **`NotImplementedException` on base nodes** — `CompositeNode.execute()` and `DecoratorNode.execute()` throw; forces subclass override
64 changes: 64 additions & 0 deletions .agents/conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Conventions & Patterns

## Language & Formatting

- All source is **Haxe 4.3.7** (see `.haxerc`).
- Use `using Lambda;` and `using StringTools;` for static extensions where applicable.
- Cross-platform types from `dropecho.interop`: `AbstractMap<K,V>`, `AbstractArray<T>`, `AbstractFunc.*`.

## Expose & NativeGen Annotations

- `@:expose("module.ClassName")` on every class exported to JS/C# (e.g. `@:expose("bt.BehaviorTree")`).
- `@:nativeGen` on classes that require native C# class generation (e.g. `FSM`, `Transition`, `IState`).
- `@:nativeChildren` on interfaces whose implementations should be natively generated.

## NODE_STATUS Usage

- Always use the enum abstract values: `NODE_STATUS.SUCCESS`, `NODE_STATUS.FAILURE`, `NODE_STATUS.RUNNING`.
- Never compare against raw integers (`0`, `1`, `2`) — the enum abstract handles conversion transparently.
- The JS target has a companion `NODE_STATUS_IMPL` class (guarded with `#if js`) for runtime exposure.

## Cross-Platform Compatibility

- Use `AbstractMap<K,V>` and `AbstractArray<T>` from `dropecho.interop` in all public APIs — JS callers pass plain objects/arrays, C# callers pass native types.
- Use `AbstractFunc.Func_0<R>`, `AbstractFunc.Action_1<A>`, etc. for callable types in public APIs.
- Avoid JS-specific APIs outside of `#if js ... #end` guards.

## Abstract Base Nodes

- `CompositeNode.execute()` and `DecoratorNode.execute()` throw `NotImplementedException` — subclasses must override.
- Do not instantiate `CompositeNode` or `DecoratorNode` directly.

## BT Node State

- Composite nodes use `CurrentIterator<Node>` to track position across ticks — reset it in the same call where you return a terminal status (SUCCESS or FAILURE).
- Do not store per-tick state on the `Blackboard` unless it must persist across tree restarts.

## GOAP Conventions

- `Action` fields are PascalCase (`ActionType`, `Cost`, `Preconditions`, `Postconditions`) — follow this for consistency.
- Preconditions and Postconditions are `Array<String>` — use descriptive lowercase strings as condition names (e.g. `"has_weapon"`, `"enemy_dead"`).
- `Planner` sorts actions by `Cost` ascending on construction — don't mutate `_availableActions` after that.

## Commit Conventions (Angular style)

Used by semantic-release to determine version bumps:

| Prefix | Bump |
|---|---|
| `feat:` | minor |
| `fix:` | patch |
| `chore:` | none |
| `refactor:` | none |
| `perf:` | none |
| `test:` | none |
| `BREAKING CHANGE:` in footer | major |

## What Not To Do

- Do not add comments explaining *what* code does — names should do that.
- Do not mock `Blackboard` or `TaskBank` in tests — use real instances.
- Do not commit anything under `dist/` or `artifacts/` — they are build outputs.
- Do not use `git add -A`; stage specific files.
- Do not compare `NODE_STATUS` values as raw integers.
- Do not add runtime dependencies; the library must stay dependency-light.
80 changes: 80 additions & 0 deletions .agents/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Development Guide

## Building

```bash
# Install deps (also runs lix download)
npm install

# Build all targets (JS CJS, JS ESM, C#, docs)
npm run build # → haxe build.hxml

# Clean build outputs
npm run clean # → rm -rf dist artifacts
```

The build entry point is `build.hxml`, which includes:
- `targets/js.hxml` — CommonJS output
- `targets/js-esm.hxml` — ES module output
- `targets/cs.hxml` — C# DLL
- `targets/docs.hxml` — XML docs

## Running Tests

```bash
# Run full test suite
npm run test # → haxelib run dropecho.testing

# Or directly (compiles and runs test.hxml)
haxelib run dropecho.testing
```

Tests compile to `artifacts/js_test.cjs` via `hxnodejs`. The `test.hxml` file defines the test build.

## Adding a New BT Node

1. Decide type: composite (multiple children) or decorator (single child).
2. Subclass `CompositeNode` or `DecoratorNode` in the appropriate subfolder.
3. Override `execute()` — return `NODE_STATUS.SUCCESS`, `FAILURE`, or `RUNNING`.
4. Add `@:expose("bt.YourNodeName")` for JS/C# export.
5. Add a test file `test/bt/node/<subfolder>/YourNodeTests.hx` extending `utest.Test`.
6. Test against the actual node using a real `Blackboard` and registered `TaskBank` tasks.

## Adding a New FSM State

Users implement `IState` directly in their own code — no library changes needed. The `FSM` class is the only place to extend for new transition features.

## Adding a New GOAP Action Type

`Action` is designed to be instantiated directly with constructor parameters. For reusable action templates, create factory functions in user code rather than subclassing.

## Project Config

`.dropecho.testing.json` controls the test runner:
```json
{
"hxml": "test.hxml",
"instrument": {
"coverage": false,
"profiler": false,
"coverage_reporter": "lcov"
}
}
```

## CI

CI runs via GitHub Actions:
- `.github/workflows/ci.yml` — build + test on push/PR
- `.github/workflows/add-to-project.yml` — GitHub project automation

## Release

Releases are fully automated via semantic-release on merge to `main`. Commit messages must follow the Angular convention (see `conventions.md`). The release pipeline:
1. Analyzes commits since last release
2. Bumps version in `package.json` and `haxelib.json`
3. Updates `CHANGELOG.md`
4. Publishes to npm (`@dropecho/ai`) and haxelib (`dropecho.ai`)
5. Creates a GitHub release

`chore(release):` commits are generated by semantic-release itself (CI is skipped via `[skip ci]`).
79 changes: 79 additions & 0 deletions .agents/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# dropecho.ai — Project Overview

## What It Is

`dropecho.ai` (npm: `@dropecho/ai`) is a game AI utilities library written in Haxe. It compiles to multiple targets including JavaScript (CJS + ESM) and C#. The library is designed for game developers who need classic AI primitives — behavior trees, finite state machines, goal-oriented action planning, and utility scoring — in a cross-platform package.

## Modules

### Behavior Tree (BT)

Node-based hierarchical behavior execution. Each node returns a `NODE_STATUS` (`SUCCESS`, `FAILURE`, or `RUNNING`) per tick.

- **`BehaviorTree`** — root wrapper around a single child `Node`; holds a `Blackboard` context
- **`Node` / `IExecutable<T>`** — core interfaces (`init(context)` + `execute()`)
- **`NODE_STATUS`** — enum abstract over `Int`: `SUCCESS=0`, `FAILURE=1`, `RUNNING=2`
- **`TaskNode`** — leaf node that looks up and calls a named task from `TaskBank`
- **`TaskBank`** — static registry mapping task names to `Blackboard → NODE_STATUS` functions
- **`Blackboard`** — shared key→Int fact store passed through the tree

Composite nodes (multiple children):
- **`SelectorNode`** — tries children in order; returns SUCCESS on first success, FAILURE if all fail
- **`SequenceNode`** — runs children in order; returns FAILURE on first failure, SUCCESS when all succeed

Decorator nodes (single child wrapper):
- **`InverterNode`** — flips SUCCESS↔FAILURE, passes RUNNING through
- **`RepeaterNode`** — always returns RUNNING (repeats child indefinitely)
- **`RepeatUntilNode`** — returns RUNNING until child returns FAILURE, then returns SUCCESS
- **`SucceederNode`** — always returns SUCCESS (or RUNNING if child is RUNNING)

### Finite State Machine (FSM)

Transition-based state management with typed conditions.

- **`FSM`** — holds current state; `tick()` runs state and checks transitions
- **`IState`** — interface: `getName()`, `onEnter()`, `onExit()`, `tick()`
- **`Transition`** — pairs a target `IState` with a `Func_0<Bool>` condition
- Supports both **state-specific** transitions (`addTransition`) and **global** transitions (`addAnyTransition`)
- `toDot()` exports the graph as Graphviz DOT format

### GOAP (Goal-Oriented Action Planning)

Backward-chaining planner that builds an ordered action sequence to satisfy a goal.

- **`State`** — a goal: list of string preconditions + relevance score
- **`Action`** — has `ActionType`, `Cost`, `Preconditions[]`, `Postconditions[]`, `UpdateFunc`, `PreMatcher`, `PostMatcher`
- **`Planner`** — given a `State` goal and available `Action[]`, generates a `Plan` via backward chaining
- **`Plan`** — ordered `Queue<Action>`; `update(dT)` ticks the current action; dequeues when postconditions satisfied

### Utility AI

Simple motive-based scoring for choosing the highest-priority behavior.

- **`Motive`** — name + float value
- **`UtilityAgent`** — holds a list of motives; `getMostImportantMotive()` returns the motive with the lowest value

### Shared Utilities

- **`CurrentIterator<T>`** — array iterator that tracks current position; supports `reset()` and `current()` without advancing
- **`NotImplementedException`** — extends `haxe.Exception`; thrown by abstract base nodes

## Package Metadata

| Field | Value |
|-------|-------|
| Haxelib name | `dropecho.ai` |
| npm package | `@dropecho/ai` |
| Version | 1.0.0 |
| License | MIT |
| Author | Benjamin Van Treese |
| Classpath | `src/` |
| Haxe version | 4.3.7 |
| JS exports | CJS (`dist/js/cjs/index.cjs`), ESM (`dist/js/esm/index.js`) |

## Build Targets

- JavaScript CJS (Node.js / bundlers)
- JavaScript ESM (modern browsers / bundlers)
- C# (.NET)
- Docs (XML documentation)
Loading