# v1.3 Features

***

## DSL Expressions

Requirements and effects can be configured via a compact **expression string** in the Inspector instead of filling individual fields. Set the `Expression` field on any `Requirement` or `Effect`; it is parsed once during `Initialization()`.

### Requirement DSL

```
key op value
```

Supported operators: `==` `!=` `>` `<` `>=` `<=`

| Expression        | Meaning                          |
| ----------------- | -------------------------------- |
| `"health > 50"`   | `health` must be greater than 50 |
| `"hasItem == 1"`  | `hasItem` must equal 1           |
| `"ammo != 0"`     | `ammo` must not be 0             |
| `"stamina <= 30"` | `stamina` must be 30 or less     |

### Effect DSL

```
key = value          (Set — overwrite)
key += value         (Add — increment)
key -= value         (Minus — decrement)
```

With optional conditions (applied at runtime and during planner simulation):

```
key op value if condKey op condValue [and|or condKey op condValue ...]
```

| Expression                                        | Meaning                                    |
| ------------------------------------------------- | ------------------------------------------ |
| `"score += 10"`                                   | Always add 10 to score                     |
| `"ammo -= 1"`                                     | Always subtract 1 from ammo                |
| `"hasItem = 0 if hasItem >= 1"`                   | Clear hasItem only when it was set         |
| `"ammo -= 1 if playerInSight == 1 and ammo > 0"`  | Consume ammo only when shooting and loaded |
| `"isAlert = 1 if sound > 0 or playerNearby == 1"` | Alert on noise or proximity                |

***

## Conditional Effects

Effects can be made conditional by populating the **Conditions** list on an `Effect`, or by using the `if` clause in a DSL expression. Multiple conditions are combined **left-to-right** using each condition's `LogicalOperator`:

| Operator | Behaviour                                           |
| -------- | --------------------------------------------------- |
| `And`    | Both this and the previous condition must be true   |
| `Or`     | Either this condition or the previous is sufficient |

Conditions are evaluated in both the **A\* simulation** (planner) and at **runtime** (action completion), keeping planning and execution fully consistent. Effects whose conditions are not met are silently skipped in both contexts.

```csharp
// Equivalent DSL: "ammo -= 1 if playerInSight == 1 and ammo > 0"
// Configured in Inspector as an Effect with:
//   Key = "ammo", Value = 1, Mode = Minus
//   Conditions[0]: Key="playerInSight" Op=Equal Value=1  LogicalOperator=And
//   Conditions[1]: Key="ammo"          Op=GreaterThan Value=0  LogicalOperator=And
```

***

## Parametric Actions

A single `Action` subclass can serve multiple roles using named float **parameters** configured in the Inspector. This eliminates repetitive one-variant-per-class patterns.

**Inspector setup** — add entries to the **Parameters** list on any action (key-value pairs):

| Key      | Value |
| -------- | ----- |
| `damage` | `50`  |
| `range`  | `8`   |

**Subclass code:**

```csharp
[System.Serializable]
public class AttackAction : Action
{
    public override bool PostPerform()
    {
        float dmg   = GetParameter("damage", 10f);  // returns 50 (or 10 if key absent)
        float range = GetParameter("range", 5f);     // returns 8

        // Apply damage, check range, etc.
        return true;
    }
}
```

`GetParameter(key, defaultValue)` returns the configured value, or `defaultValue` when the key is absent. `HasParameter(key)` returns `true` when a parameter with that key exists on this instance.

**Per-instance configuration:** the same `AttackAction` class can be added twice to a behavior with different `damage` and `range` values, each appearing as a distinct action in the plan.

***

## Plan Ordering

Enforce execution order between actions by embedding a constraint in the action's **Name** field using the bracket DSL:

```
DisplayName[After]TargetName         — runs after TargetName (default seed=0, trigger=1)
DisplayName[Before]TargetName        — runs before TargetName
DisplayName[After]TargetName(T)      — custom trigger value T
DisplayName[After]TargetName(S)(T)   — custom seed value S and trigger value T
```

The optional parenthesis suffixes control the `__order_*` state key values:

| Suffix form | State seed | Requirement / Effect value |
| ----------- | ---------- | -------------------------- |
| *(none)*    | `0`        | `1`                        |
| `(T)`       | `0`        | `T`                        |
| `(S)(T)`    | `S`        | `T`                        |

* **Seed value** — the initial value written into `State` for the `__order_*` key at startup, re-enable, and goal-sequence repeat.
* **Trigger value** — the value injected as both the requirement check (`== trigger`) and the effect (`= trigger`).

Examples:

| Name field                  | Constraint enforced                                  |
| --------------------------- | ---------------------------------------------------- |
| `"UseItem[After]Cover"`     | UseItem runs after Cover (seed=0, trigger=1)         |
| `"Reload[Before]Shoot"`     | Reload always precedes Shoot                         |
| `"Fire[After]Reload(2)"`    | Fire runs after Reload; ordering key checks/sets `2` |
| `"Fire[After]Reload(0)(2)"` | As above, seed is explicitly `0`; trigger is `2`     |

**How it works:** on `Start()`, `AIBehavior.ApplyOrderingConstraints()` reads each action's name DSL and injects implicit hidden state keys:

* `A[After]B` → injects requirement `__order_B == triggerValue` on A and effect `__order_B = triggerValue` on B
* `A[Before]B` → injects effect `__order_A = triggerValue` on A and requirement `__order_A == triggerValue` on B

The `__order_*` key is also seeded in `State` with the **seed value** at startup so the key exists for `Has()` checks and initial plan validation.

These `__order_` keys behave like any other state key — the planner finds them naturally during A\* search. No planner changes are required.

**Name extraction:** the `Name` property returns only the display part before `[`, so `"UseItem[After]Cover"` appears as `"UseItem"` in logs and the debugger.

> **Edge case:** do not set `IsIncludeOnPlanning = false` on an action that is the **target** of an `[After]` or `[Before]` constraint. If the target action is excluded from planning, the `__order_*` key it would write to State will never be set, causing a replan loop on the dependent action.

***

## Goal Evaluator

Override the planner's default A\* scoring function to bias plan search toward game-specific priorities.

**Default behaviour:** h(n) = count of unsatisfied goal conditions. Admissible (never overestimates), guaranteeing optimal plans.

**Custom scoring:** assign a `Func<PlanStateContext, float>` to `AIBehavior.GoalEvaluator`. The delegate is called at every node expansion with a `PlanStateContext` that provides read-only access to the simulated state.

```csharp
// Urgency: prefer plans that satisfy low-health states sooner
GetComponent<AIBehavior>().GoalEvaluator = ctx =>
{
    float hp = ctx.Get("health");
    if (float.IsNaN(hp)) return 1f;    // health not in state — use neutral cost
    return hp < 30f ? 0.5f : 1f;       // half-cost when critically low
};
```

```csharp
// Resource pressure: more unsatisfied goals = higher urgency
GetComponent<AIBehavior>().GoalEvaluator = ctx =>
{
    float score = 0f;
    if (!ctx.Has("ammoLoaded"))    score += 2f;   // ammo shortage is critical
    if (!ctx.Has("isInCover"))     score += 1f;
    if (!ctx.Has("atPatrolPoint")) score += 0.5f;
    return score;
};
```

**`PlanStateContext` API:**

| Member            | Description                                                                         |
| ----------------- | ----------------------------------------------------------------------------------- |
| `Get(string key)` | Returns the simulated float value for `key`, or `float.NaN` if not set in this node |
| `Has(string key)` | Returns `true` when `key` is registered and has a value in this node's state        |

> **Thread safety:** `GoalEvaluator` runs on the planner's background thread. Do **not** access `Transform`, `Time`, `NavMeshAgent`, or any Unity API from inside the delegate — read only from the `PlanStateContext` parameter.

> **Admissibility:** if the returned value consistently exceeds the true remaining cost, the planner may produce sub-optimal (but still valid) plans, and search will be faster. Returning `0` always degrades A\* to Dijkstra's algorithm.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://beelabs-dev.gitbook.io/beelabs-docs/goap-engine/v13-features.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
