---
title: "Choose a Game Character's Next Action"
description: "Turn a character's state into JSON, ask one choice question over a fixed action list, and get the pick plus a probability for every option. One request replaces a hand-written if-else tree."
canonical: "https://system-one.dev/en/use-cases/npc-action"
language: "en"
---

# Choose a Game Character's Next Action

**Key points**

1. Game AI is full of "pick one move from a fixed list" decisions, usually hand-written as if-else trees.
2. A choice question returns the selected action with the probability behind every option, so the game loop can use the pick directly and react to how close the second option was.
3. Design-time rules stay in your code: the action list, the cooldowns and any hard constraint are yours, not the model's.

## Why a fixed action list fits

A character's next move is a bounded decision: attack, drink a potion, flee, ask for a truce, taunt. Nothing is generated, so the game loop receives one string it can switch on.

Writing that as if-else means every state variable needs a threshold someone guessed. A probability per action lets you see how decisive the pick was, which is useful for animation, for difficulty tuning, or for spotting states where the character dithers.

## Question shape

```ts
import { choice } from '@system-one-ai/core';

export const actionQuestion = {
  next: choice('What should this character do next?', {
    attack: 'Close distance and attack the nearest enemy',
    potion: 'Drink a healing potion before acting',
    flee: 'Retreat toward the nearest exit',
    truce: 'Attempt to negotiate a pause',
    taunt: 'Provoke the enemies to draw them out',
  }),
};
```

Send the state the character actually has: health, enemies and their distance, inventory, terrain, and whether an ally is nearby.

> Illustrative code, not tested. The thresholds are placeholders.

```ts
export async function nextAction(client, state: {
  healthPct: number; enemies: number; nearestEnemyM: number;
  potions: number; allyNearby: boolean; terrain: string;
}) {
  const result = await client.evaluate({ state, questions: actionQuestion });
  const pick = result.answers.next.choice;
  const confidence = result.answers.next.confidence;
  // Placeholder: let a scripted behaviour take over when the model is unsure
  return confidence >= 0.5 ? pick : 'hold_position';
}
```

## What to keep in code

- **The action list itself.** Options are game design, not model output.
- **Hard constraints.** Cooldowns, "no potions while silenced", and anything the rules engine already enforces should short-circuit before the call.
- **The fallback.** When confidence is low, a scripted behaviour is often better than a coin flip.

## Latency and cost

The demo on this site shows sample data, not a live run; measure latency in your own game before calling this per frame. A typical call is one decision per turn, not per frame.

Every 4KB is one block, and anything smaller still counts as one block. Cost = blocks × questions × the model rate. A short character state fits in one block.

New accounts start with 100 credits, and credits never expire. See [pricing](https://system-one.dev/en/pricing).

[Run the character sample (sign-in required)](https://system-one.dev/en/demo/npc-action) to see the same question on sample data.


## Frequently asked questions

### Are the probabilities on this page computed live?

No. The demo shows sample data and does not change when you switch models. Sign in to run your own character state.

### Can I define my own actions?

Yes. The model picks from whatever you list as options; the maximum option count is on each model's page.

### Is it fast enough to call every frame?

Assume not. Design for one call per turn and measure latency on your own hardware before anything tighter.

### Should the model own cooldowns and rules?

No. Keep hard constraints in the rules engine and only send states that are already legal to act on.
