← Back to Journal
Designing the Game State for AI

leptons1618@anishgiri.dev:~/journal/designing-the-game-state-for-ai less post.md

#AI#MACHINE_LEARNING#PYTHON#DATA_SCIENCE#PERFORMANCE#OBSERVABILITY

Designing the Game State for AI

Designing game state representations for AI agents: raw vs derived features, state projections, versioning, stale decision handling, and deterministic baselines.

Most discussions about AI game agents start with the model.

That is probably backwards.

If an AI is supposed to make a decision inside a game, the first question shouldn’t be:

Which model should I use?

It should be:

What exactly does the model get to know?

A game already contains an enormous amount of structured information:

player position
enemy position
health
ammo
weapons
objectives
cover
visibility
nearby enemies
recent damage
team state
map state

The AI does not automatically understand any of it.

The game has to turn that world into a representation the model can actually consume.

That representation is the game state.

And designing that state is arguably one of the most important parts of building a useful AI system.


The architecture I want to test

My previous article looked at TypeSafe AI as a structured decision layer rather than a traditional text-generating LLM.

The architecture looked roughly like this:

Game State
    ↓
Decision Model
    ↓
Typed Decision
    ↓
Game Logic

The next problem is obvious:

What goes inside Game State?

I want to build a small experiment around exactly that question.

The experiment will use a simulated NPC and compare different representations of the same game situation.

                Game Simulator
                      │
                      ▼
                 Raw State
                      │
                      ▼
                State Builder
                      │
              ┌───────┴────────┐
              │                │
          raw state       derived state
              │                │
              └───────┬────────┘
                      ▼
                AI Decision
                      │
                      ▼
              Decision Validator
                      │
                      ▼
              Deterministic Logic

The important part is that the AI doesn’t control the game.

It recommends.

The game decides.


1. Start with the raw game state

Suppose I have a simple combat NPC.

The simulator knows:

{
  "npc": {
    "health": 23,
    "ammo": 2,
    "morale": 0.42,
    "weapon": "shotgun"
  },
  "player": {
    "health": 72,
    "distance": 8,
    "recent_damage": 47
  },
  "environment": {
    "cover_available": true,
    "cover_quality": 0.8,
    "enemy_count": 1,
    "ally_count": 0,
    "line_of_sight": true
  }
}

This is already useful.

But it isn’t necessarily the best representation for a decision model.

The model has to infer things such as:

health is low
ammo is low
the NPC recently took significant damage
the player is close
cover exists
the NPC is outnumbered

That raises an interesting design question:

Should the AI have to infer those things itself?


2. Raw state vs derived state

There are two obvious approaches.

Raw state

{
  "health": 23,
  "ammo": 2,
  "distance": 8,
  "recent_damage": 47,
  "cover_available": true
}

Derived state

{
  "health": 23,
  "ammo": 2,
  "distance": 8,
  "recent_damage": 47,
  "cover_available": true,

  "health_ratio": 0.23,
  "low_ammo": true,
  "under_pressure": true,
  "escape_possible": true
}

The second representation gives the model more semantic information.

But it also introduces another engineering problem.

Who should calculate those derived values?

My preference is:

The game should calculate deterministic derived features.

For example:

health_ratio = health / max_health

low_ammo = ammo <= 2

under_pressure = (
    recent_damage > 30
    and distance < 15
)

There is little reason to spend an AI inference call determining something that ordinary code can calculate exactly.

This gives us a useful boundary:

Deterministic computation
        ↓
Game code

Semantic judgment
        ↓
AI

That distinction becomes increasingly important as the system grows.


3. Don’t send the entire game

A game might contain thousands of pieces of state.

The AI probably doesn’t need all of them.

For an NPC combat decision, these probably matter:

health
ammo
enemy distance
recent damage
cover
enemy count
ally count
line of sight
escape route

These probably don’t:

NPC cosmetic skin
texture ID
particle effect ID
animation asset path
sound asset ID
unrelated quest metadata

This sounds obvious.

It isn’t always obvious in real systems.

As applications grow, state objects tend to become enormous because every subsystem keeps adding another field.

Eventually someone says:

“Just send the whole object.”

And thus another software architecture is born.

The better approach is to define an explicit AI state projection.

Full Game State
      │
      ▼
AI State Builder
      │
      ▼
Relevant State

The AI should receive a deliberate view of the world rather than the entire world.


4. State is a contract

Once the AI depends on game state, that state becomes an interface.

For example:

class GameState(BaseModel):
    tick: int
    timestamp_ms: int
    npc: NPCState
    player: PlayerState
    environment: EnvironmentState

That contract should be:

  • explicit
  • versioned
  • validated
  • observable
  • reproducible

I would therefore include:

{
  "schema_version": 1,
  "state_version": 184
}

The distinction between those two versions matters.

schema_version means:

What structure does this state follow?

state_version means:

Which point in the simulation does this state represent?

That second value becomes extremely useful once AI calls become asynchronous.


5. The stale decision problem

Imagine the game is running normally.

At tick 184:

NPC health = 30
enemy distance = 12
cover = available

The game sends this state to the AI.

state_version = 184

While the AI is thinking, the game continues.

The player shoots the NPC.

Now the current state is:

state_version = 187
health = 4

Then the AI responds:

state_version = 184
decision = attack

If the game blindly executes that decision, the NPC may attack based on information that is already obsolete.

That’s a serious architectural problem.

The solution is simple:

Every AI decision should reference the state it was generated from.

{
  "state_version": 184,
  "choice": "attack"
}

Before applying it:

if decision.state_version != current_state.version:
    reject(decision)

Now stale decisions become a normal software condition rather than a mysterious AI bug.


6. Don’t ask one giant question

Another experiment I want to test is how much we should ask the model to do in one request.

Instead of:

Given the complete game state,
reason about everything and decide what
the NPC should do.

I would break it down.

For example:

Is the NPC currently under significant threat?

How favorable is the current combat situation?

How useful would retreating be?

What should the NPC do next?

These are much easier to reason about as separate typed decisions.

Conceptually:

Game State
    │
    ├── Threat?
    │
    ├── Combat score?
    │
    ├── Retreat value?
    │
    └── Action?

The application can then combine those results.

This is also useful for debugging.

If the NPC makes a bad decision, I can inspect:

Threat: true
Combat: very_unfavorable
Retreat value: 5
Action: attack

Now there is an obvious place to investigate.

Without decomposition, you get:

AI said attack.

Excellent. Very scientific. We have learned approximately nothing.


7. The deterministic system still owns the action

Even if the AI returns:

attack

that does not mean the NPC can necessarily attack.

The application still needs to validate it.

if decision.choice == "attack":
    if npc.ammo <= 0:
        action = "reposition"

    elif not can_attack(state):
        action = "seek_cover"

    else:
        action = "attack"

This creates a useful separation:

AI:
"What seems appropriate?"

Game:
"Is that actually allowed?"

The AI handles semantic judgment.

The game handles invariants.

That is a much safer architecture than allowing a model to directly control game state.


8. Confidence changes the architecture

A structured decision becomes more interesting when it also has uncertainty.

Suppose the AI returns:

{
  "choice": "seek_cover",
  "confidence": 0.84
}

The application can define a policy:

if confidence >= 0.80:
    accept()

elif confidence >= 0.60:
    accept_with_validation()

else:
    use_fallback()

The exact thresholds need to be measured rather than pulled from the sacred book of Arbitrary Numbers.

And confidence shouldn’t be treated as proof that an answer is correct.

It is a signal that needs evaluation and calibration.


9. Latency changes everything

There is another reason I don’t want AI inside the actual frame loop.

At 60 FPS:

1 frame = 16.67 ms

An AI request might take substantially longer.

Therefore:

Frame
  ↓
AI request
  ↓
wait
  ↓
response
  ↓
next frame

is the wrong architecture.

Instead:

Game Loop
    │
    ├── update
    ├── render
    ├── execute current behavior
    └── publish AI snapshot
              │
              ▼
         AI inference
              │
              ▼
        decision queue
              │
              ▼
          validator
              │
              ▼
       future behavior

The NPC continues operating while the AI is thinking.

That means AI becomes an asynchronous decision source rather than a synchronous part of the frame pipeline.


10. State freshness becomes another variable

Once decisions are asynchronous, state age matters.

Consider:

Decision A
generated from state 100
age = 20 ms

Decision B
generated from state 101
age = 150 ms

Which one should be used?

There isn’t one universal answer.

Different decisions have different freshness requirements.

For example:

combat action:
~100 ms

cover selection:
~300 ms

dialogue choice:
~2 seconds

quest recommendation:
much longer

So I want the experiment to treat freshness as a first-class property.

{
  "state_version": 184,
  "created_at": 1758512200000,
  "expires_at": 1758512200300
}

Now the application can reason about whether an AI decision is still useful.


11. Build a deterministic baseline

This is probably the most important part of the experiment.

I don’t want to build an AI system and then declare victory because the NPC moved around and looked intelligent.

I want a baseline.

Something simple:

def baseline_action(state):
    if state.npc.health < 20:
        return "retreat"

    if state.environment.cover_available:
        if state.player.recent_damage > 30:
            return "seek_cover"

    if state.player.distance < 10:
        return "attack"

    return "idle"

Then run the exact same game states through:

                 Same state
                     │
          ┌──────────┴──────────┐
          ▼                     ▼
     Rule-based AI        Semantic AI
          │                     │
          └──────────┬──────────┘
                     ▼
                 Evaluator

Now there is something measurable.


12. The experiments

I want to test at least five versions of state.

Experiment A: Minimal state

health
ammo
distance
cover
recent damage

Experiment B: Derived state

Add:

health ratio
low ammo
under pressure
escape possible

Experiment C: Temporal state

Add:

recent actions
time since damage
damage received recently
previous decision

Experiment D: Irrelevant state

Intentionally add things such as:

cosmetic ID
skin
unrelated metadata

Then see whether decisions become less stable.

Experiment E: Stale state

Artificially delay responses and measure what happens when decisions arrive late.

This is much more interesting than simply asking:

“Does AI work?”

The better question is:

Which representation of the game state produces useful, stable decisions?


13. What I want to measure

Every decision should generate a record like:

{
  "tick": 421,
  "state_version": 184,
  "state_size_bytes": 812,
  "decision": "seek_cover",
  "confidence": 0.84,
  "latency_ms": 142,
  "state_age_ms": 34,
  "fallback": false,
  "baseline_action": "seek_cover",
  "evaluator_score": 5
}

Then I can measure:

Latency

p50
p95
p99

Reliability

timeouts
invalid responses
stale responses
fallback rate

Decision behavior

baseline agreement
evaluator score
decision distribution

State characteristics

serialized state size
number of fields
derived fields
temporal context size

Now the blog isn’t just:

“I think this architecture is interesting.”

It becomes:

“I tested three state representations across N simulated decisions and here’s what changed.”

That is a much more useful engineering article.


14. The architecture I’m aiming for

The final system should look roughly like this:

                         GAME
                           │
                           ▼
                  ┌─────────────────┐
                  │ Game Simulation  │
                  └────────┬────────┘
                           │
                       raw state
                           │
                           ▼
                  ┌─────────────────┐
                  │  State Builder  │
                  │                 │
                  │ validation      │
                  │ derived values  │
                  │ versioning      │
                  └────────┬────────┘
                           │
                     AI state
                           │
                           ▼
                  ┌─────────────────┐
                  │ Decision Model  │
                  │                 │
                  │ Choice          │
                  │ Score           │
                  │ Boolean/Noul    │
                  └────────┬────────┘
                           │
                  decision + confidence
                           │
                           ▼
                  ┌─────────────────┐
                  │   Validator     │
                  │                 │
                  │ freshness       │
                  │ confidence     │
                  │ allowed action  │
                  └────────┬────────┘
                           │
                           ▼
                  ┌─────────────────┐
                  │ Behavior System │
                  │                 │
                  │ rules / BT /    │
                  │ utility logic   │
                  └────────┬────────┘
                           │
                           ▼
                         GAME

The important boundary is here:

        Semantic uncertainty
                 │
                 ▼
                AI
                 │
                 ▼
       Deterministic execution

The AI doesn’t need to own the whole system.

It needs to own the part that ordinary rules struggle to express.


15. The experiment I actually care about

The interesting question isn’t whether a model can produce:

attack

A model can do that.

The interesting question is whether the representation of the world changes the quality of that decision.

For example:

State A

health = 23
ammo = 2
distance = 8
cover = true
recent_damage = 47

versus:

State B

health = 23
ammo = 2
distance = 8
cover = true
recent_damage = 47

health_ratio = 0.23
low_ammo = true
under_pressure = true
escape_possible = true

versus:

State C

everything above

+ recent actions
+ damage history
+ previous decisions
+ temporal context

Does more information help?

Does derived information help?

At what point does additional context stop helping?

Does irrelevant state create instability?

How quickly does a decision become stale?

How much latency can the architecture tolerate?

Those are engineering questions.

And they are much more interesting than simply asking which AI model is “smarter.”


16. The broader lesson

The more I think about AI inside conventional software, the more the model itself starts looking like only one component.

The real architecture becomes:

World
 ↓
State representation
 ↓
Semantic inference
 ↓
Validation
 ↓
Deterministic execution
 ↓
World

The difficult part isn’t only inference.

It is deciding:

What does the model know?

What does it not know?

When was that information true?

Which parts should be calculated deterministically?

How much context is enough?

What happens when the model is wrong?

What happens when the response arrives late?

Who owns the final action?

That makes state design an AI engineering problem in its own right.


Final takeaway

I started this experiment thinking the interesting part would be the AI decision model.

I’m increasingly convinced the more important component is the boundary around it.

A useful game AI system probably shouldn’t look like:

Game → AI → Everything

It should look more like:

Game
 ↓
Carefully designed state
 ↓
AI semantic judgment
 ↓
Typed decision + uncertainty
 ↓
Validation
 ↓
Deterministic game logic

The AI gets a structured view of the world.

The application decides what that view means operationally.

And the next experiment is to find out how much the quality of the decision depends on how that world was represented in the first place.