"LLMs are bad at games." Game developers say it often. The same model that whips up a web app in minutes falls apart on a game. Why?

Game developer Kyle Kukshtel turned that question into a 45-minute talk at Indie Rodeo in Calgary on September 12, then published it as an essay on October 10. His conclusion is blunt.

LLMs are structurally bad at reading games,
because games are not made of code alone.
And structurally, that is a context problem, not a model problem.

A game is a black box where code, assets (meshes, sounds, textures), data, and runtime state are tangled together. Unlike web pages that place static images and text, games are full of things that code does not fully represent. So the model sees code — never the game.

This piece follows Kukshtel's talk and distills seven design moves that make a game legible to AI agents — and shows how the same instincts transfer beyond games, to web and app work.

Why it happens: a context problem

Start with the exact cause. The talk opens with how LLM prompts actually work.

Every LLM call splits into three parts:
  System prompt      — shared text the service attaches
  Assistant prompt   — orients what the model is responding to
  User prompt        — where your actual work goes

And sessions: every message you send re-submits everything
before it, every time. Like a playground game where each turn
requires repeating what everyone else said first.

Two consequences follow. First, longer contexts carry older bad information forward — context rot. Second, the more of a game lives outside code, the less of the thing the model needs to understand actually reaches the context. In Kukshtel's words: getting LLMs to work well with games is a context-engineering problem, not something intrinsic to how games or models "are."

Seven design moves: making a game explainable to AI

The heart of the talk. Each item is good game development on its own; wired to an agent, its value multiplies.

1. Fixed randomness: the root of reproducibility

Create a single seeded randomness source at game start;
route every non-deterministic call through it.

-> The same seed deterministically recreates almost any state
-> An agent can retry from the same starting point, over and over

An agent iterating on a fix needs to reach the same state every attempt. Without reproducibility, you cannot tell whether a change helped at all.

2. Screenshots and image diffing: turning progress into numbers

- Use the engine's screenshot API (add UI-layer toggles to cut clutter)
- Diff before/after screenshots with a tool — e.g. odiff
  -> you get a quantitative value like "85% similar"
- The agent optimizes toward that number instead of guessing
  from raw images

Handing before/after images to a multimodal model often muddies its sense of progress; the whole image is context noise. An image-diff tool converts that noise into a stable metric the loop can optimize.

3. Deterministic state: save/load from day one

- Save/load is not a late "feature" — it is a development superpower.
  Design it before the first line of code.
- Think "Thing-driven": treat every object as effectively one type
  (a tree and a dog are both a mesh, a collider, and some stats)
- One object type means one serialization path, one place to edit.
  The AI knows exactly where changes go, every time.

This is the most practical section of the talk. A hundred class hierarchies force the model to understand all of them. A single fat-struct type gives it one source of truth — and makes save/load trivial.

4. Command pattern: change state without compiling

Express state changes as serializable command objects,
not loose function calls.

  [start state] + [replay commands] = [end state where the bug happened]

- Reproduce bugs by replaying commands, not interviewing the player
- Commands are text: they can be authored outside the game
  -> the AI can puppet the game through a typed interface
  -> and no attempt ever needs a compile

Compile waits kill agent loops. Command replay shrinks "try, check, adjust" to seconds.

5. Data outside the game: iterate without builds

Keep data (types, attack damage, walk speed) in an external data
source, not in code. Deserialize it at load time.

- Cantata: about seven non-programmers edited data in Depot while
  builds of the game loaded the changes — no developer builds needed
- AI agents can learn the same tool and, like a teammate, test data
  changes without rebuilding

Remove the build from data changes, and the agent experiments more often and learns faster.

6. Separate simulation: pull the core out of the engine

Build the game's simulation as its own library (DLL), outside the engine.

  [engine / visuals] <-> commands / states <-> [simulation core]

- The engine is "dumb": it sends commands and renders returned states
- The simulation is the source of truth. If it says you moved, you moved
- Win 1: network, tests, or an LLM can all drive the core — same interface
- Win 2: pure-code tests of the simulation, with no engine running
  -> agents verify logic without booting the whole game

7. Impact assessment: profiling that agents can read

Profiling tools are built for humans: call stacks, flame graphs, eyes.
Agents prefer text. Use tools that dump captures as text.

  e.g. samply — dump portable profile captures to the console
  -> an agent can read its own performance impact and re-evaluate

Wire these together and you get a flywheel.

[reproduce a deterministic state] -> [change it via commands]
-> [run without compiling] -> [score with image diff + text profiles]
-> [next turn]

When one loop takes seconds, you run verification on every change
instead of saving it for the changes that "matter."

Bringing it home: the game is its own harness

That is where the title lands. Every AI harness — from coding agents like Claude Code to domain-specific tooling — exists to help an agent understand what it is building and guide the process. Yet no harness actually talks to your game.

Make the game legible, and the game itself
becomes the agent's harness. Any game can do this.

The caveats, from the author himself

Kukshtel balances the essay in his postscript.

- These ideas are for larger projects. The small project he is
  currently shipping used none of them.
- There is an upfront cost. Determinism and save/load structure
  are nearly impossible to retrofit at the end; they start on day one.
- Not every game needs this. Scale and lifespan are the criteria.

And translated to web and app work, the principles hold surprisingly intact.

In a game Translated to web/apps
Seeded randomness Seeded test fixtures, deterministic fake data
Screenshot + image diff Snapshot / visual regression tests (e.g. Playwright)
Save/load as one state A single serializable state store
Command replay Event-log replay, recorded API sequences
Isolated simulation Pure domain modules, decoupled from UI
Text profiling Performance reports and benchmark output in CI

CodeBridge mini lab: bolt a verification loop onto a small project

The briefing's action, turned into an experiment:

Pick one small UI project and add three things, in order:

1. State reproduction
   [ ] seeded test data restores any screen state with one command
2. Screenshot comparison
   [ ] save a baseline, compare after changes, report the diff
3. Automated regression tests
   [ ] a few core scenarios run automatically; the agent sees failures first

Then hand an agent a task like "fix the spacing on this screen."
With the loop in place, the answer changes: the agent checks, fixes,
and re-checks by itself.

Conclusion: productivity comes from how explainable your app is

Agent productivity depends not only on model capability,
but on your application's testability and observability.

Turning a game into its own harness is really about writing code an AI can read and verify. That design lesson travels everywhere.

Further reading

References

Go deeper with a course

The designs that make a game legible to AI are best felt by building one. Make a small game with Gemini and Flutter, and see how state, reproducibility, and verification interlock.