# State Space

In order for the agents to understand the game world, we need to convert the game state into a format that it can digest - we will call this the model's "state space".

<figure><img src="/files/jMjClnZMNZTOhBEoX4nB" alt=""><figcaption></figcaption></figure>

Each model architecture will have different structure for the state space, so it is important to clearly define the features that are important to your game and the method to extract them. Developers can either use the built-in feature engineering module or create their own custom features.

### Feature Engineering Module

The NRN agents SDK comes with an ability to automatically extract features from a game world. Developers only need to define a configuration so the SDK knows how to access certain values from the game world. Below we show an example getting the following features:

* Raycasts that originate from the player and detect enemies around it
* Relative position (distance and angle) to a powerup

{% tabs %}
{% tab title="Javascript" %}

```javascript
import { FeatureEngineering } from "nrn-agents"

FeatureEngineering.setStateConfig([
  {
    type: "raycast",
    keys: { origin: "player", colliders: "enemies", maxDistance: "gameArea.width" },
    setup: { numRays: 8 }
  },
  {
    type: "relativePosition",
    keys: { entity1: "player", entity2: "items[0].powerup", maxDistance: "gameArea.width" } 
  }
])

const state = FeatureEngineering.getState(world)
```

{% endtab %}

{% tab title="C# - Unity" %}
Coming Soon
{% endtab %}
{% endtabs %}

The state config is comprised of an array of feature configs of the following format:

```typescript
{
    type: string,                 // Name of the feature type
    keys: Record<string, string>, // Keys used to extract values from the game world
    setup?: Record<string, any>   // Additional setup parameters
}
```

#### Features Available

In the configurations below, we use the notation `string -> objectType` to denote that the value for the key must be a string that points to an object of a particular type.

<details>

<summary>Raycast</summary>

Configuration

```typescript
{
    type: "raycast",
    keys: {
        origin: string -> { x: number, y: number },
        colliders: string -> { x: number, y: number, width: number, height: number }[],
        maxDistance: string -> number
    },
    setup?: { numRays: number }
}
```

Returns: Array of Length **numRays**

```javascript
[
    Ray 1, // (1 / numRays) * 360 degrees
    Ray 2, // (2 / numRays) * 360 degrees
    Ray 3, // (3 / numRays) * 360 degrees
    ...
    Ray N, // 360 degrees
]
```

</details>

<details>

<summary>Relative Position</summary>

Configuration

```typescript
{
    type: "relativePosition",
    keys: {
        entity1: { x: number, y: number },
        entity2: { x: number, y: number },
        maxDistance: number
    }
}
```

Returns: Array of Length **3**

```javascript
[
    Distance // 0 is farthest, 1 is closest
    Sin(Radians) // Direction indication #1
    Cos(Radians) // Direction indication #2
]
```

</details>

<details>

<summary>Relative Position To Cluster</summary>

Configuration

```typescript
{
    type: "relativePositionToCluster",
    keys: {
        origin: { x: number, y: number },
        clusterEntities: { x: number, y: number }[],
        maxDistance: number
    }
}
```

Returns: Array of Length **3**

```javascript
[
    Distance // 0 is farthest, 1 is closest
    Sin(Radians) // Direction indication #1
    Cos(Radians) // Direction indication #2
]
```

</details>

<details>

<summary>One Hot Encoding</summary>

Configuration

```typescript
{
    type: "onehot",
    keys: { value: string },
    setup: { options: string[] } 
}
```

Returns: Array of Length **N**, where `N = setup.options.length`

```javascript
[
    0 or 1, // 1 if option[0] == value, otherwise 0
    0 or 1, // 1 if option[1] == value, otherwise 0
    ...
    0 or 1, // 1 if option[N-1] == value, otherwise 0
]
```

</details>

<details>

<summary>Binary</summary>

Configuration

```typescript
{
    type: "binary",
    keys: { value: number | string },
    setup: { 
        operator: "=" | ">" | "<" | "!=",
        comparison: number | string
    } 
}
```

Returns: Array of Length **1**

```javascript
[
    0 or 1, // 1 if criteria is met, otherwise 0
]
```

</details>

<details>

<summary>Rescale</summary>

Configuration

```typescript
{
    type: "rescale",
    keys: { 
        value: number,
        scaleFactor: number
    }
}
```

Returns: Array of Length **1**

```javascript
[
    Rescaled value, // Number between 0 and maxPossibleNumber/scaleFactor
]
```

</details>

<details>

<summary>Normalize</summary>

Configuration

```typescript
{
    type: "normalize",
    keys: { value: number },
    setup: { 
        mean: number,
        stdev: number
    } 
}
```

Returns: Array of Length **1**

```javascript
[
    Normalized value, // Rescaled number by mean and standard deviation
]
```

</details>

### Custom Features

If developers want to create features that are currently not offered by the feature engineering module, then they are able to. Below we showcase how to convert a [Pong](https://en.wikipedia.org/wiki/Pong) game world to a state matrix with custom feature engineering:

{% tabs %}
{% tab title="Javascript" %}

```javascript
const bound = (x) => {
    return Math.max(Math.min(x, 1), -1)
}

const getStateSpace = (world) => {
    const paddlePos = {
        x: world.paddleLeft.x,
        y: world.paddleLeft.y + world.paddleLeft.height / 2
    }
    const paddleScaling = 1 - world.paddleLeft.height / world.gameArea.height
    const absolutePaddlePos = (paddlePos.y / world.gameArea.height - 0.5) * 2 / paddleScaling
    
    return [[
        absolutePaddlePos,
        (world.ball.y - paddlePos.y) / world.gameArea.height,
        bound((-world.ball.x / world.gameArea.width + 0.5) * 2),
        bound(world.ball.dx / 8),
        bound(world.ball.dy / 8)
    ]]
}

const state = getStateSpace(world)
```

{% endtab %}

{% tab title="C# - Unity" %}

```csharp
using NrnAgents.MathUtils;

namespace NrnIntegration
{
    class NrnStateSpace
    {
        private static float gameAreaHeight = 8;
        private static float gameAreaWidth = 16;
        private static float paddleHeight = 2;
        private static float PaddleScaling { get; set; }
        
        public NrnStateSpace() 
        {
            PaddleScaling = 1 - (paddleHeight / gameAreaHeight);
        }
        
        public static double bound(double x) => Math.Min(1, Math.Max(-1, x));
        
        public static Matrix GetState(NrnWorld world)
        {
            double leftPaddleY = world.leftPaddlePos.y;
            double absolutePaddlePos = (leftPaddleY / gameAreaHeight) * 2;
            double yDist = (world.ballPos.y - leftPaddleY) / gameAreaHeight;
            double xDist = bound((-world.ballPos.x / gameAreaWidth ) * 2);
            double xVel = bound(world.ballVel.x / 8);
            double yVel = bound(world.ballVel.y / 8);
            return CreateStateMatrix(new List<double> { absolutePaddlePos, yDist, xDist, xVel, yVel });
        }

        private static Matrix CreateStateMatrix(List<double> stateList)
        {
            Matrix state = new(1, stateList.Count);
            List<List<double>> nestedListState = new List<List<double>> { stateList };
            state.FillFromData(Matrix.To2DArray(nestedListState));
            return state;
        }
    }
    
    public class NrnWorld
    {
        public Vector3 ballPos { get; set; }
        public Vector3 ballVel { get; set; }
        public Vector3 leftPaddlePos { get; set; }
        public Vector3 rightPaddlePos { get; set; }
    }  
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Developers can also combine the built-in feature engineering functionality with their own custom features.
{% endhint %}


---

# Agent Instructions: 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://docs.nrnagents.ai/getting-started/state-space.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.
