Why Your AI-Made Game Feels Broken (And 7 Fixes That Actually Work)
Short answer: a generated game almost never breaks because the AI wrote bad code. It breaks because the prompt described a theme instead of a loop. The AI fills every unspecified slot with the safest possible default — an endless loop, flat difficulty, instant respawn, no feedback — and that combination reads to a player as "broken" even when nothing is technically crashing.
Below are the seven failures that account for almost every "this feels wrong" build, what each one looks like from the player's side, and the exact change that fixes it. Fix them in order. The first three cost nothing and recover most of the feel.
Quick diagnostic order
Work top to bottom. Each step is cheaper than the one below it.
| Order | Symptom a player reports | Likely cause | Time to fix |
|---|---|---|---|
| 1 | "Nothing happens when I press things" | No feedback layer | 10 min |
| 2 | "It's boring after 20 seconds" | No difficulty curve | 15 min |
| 3 | "I can't lose / I can't win" | Missing win or fail state | 10 min |
| 4 | "It feels laggy / floaty" | Frame-dependent movement | 20 min |
| 5 | "I fall through the floor" | Untuned collision | 30 min |
| 6 | "Every change breaks something else" | State sprawl | 1–2 hrs |
| 7 | "My progress vanished" | Non-atomic save | 30 min |
1. There is no feedback layer, so nothing feels like it happened
What the player says: "I pressed jump, I think? It's hard to tell."
Why it happens: a prompt like "a platformer where a fox jumps over lava" specifies objects and verbs. It says nothing about what happens on contact. The model generates correct state changes — the fox's y value decreases — and stops there. No particle, no sound, no squash, no shake. The game is functionally right and experientially silent.
The fix: add a juice pass as a separate instruction, not as part of the original prompt. Ask for four specific hooks and nothing more:
For every player action, add: (1) a 80-120ms scale squash on the sprite,
(2) 6-10 particles at the contact point, (3) a screen shake of 2-4px for
0.1s, (4) a sound trigger on the same frame as the state change.
Do not change movement values.
Four hooks is the floor. Games with four feel alive; games with twelve feel noisy. Do this before touching anything else — it is the highest return per minute in the entire list, and it is the single reason a build that "looks fine" in the editor reads as dead on a phone.
2. Difficulty is flat because escalation was never specified
What the player says: "It's the same thing forever."
Why it happens: an unspecified difficulty curve defaults to a constant. Enemy spawn interval stays at 2 seconds, speed stays at 100, forever. The model has no reason to invent a curve — a curve is a design decision, and design decisions that weren't asked for get the mean value.
The fix: describe escalation as a number, not an adjective. "Make it get harder" produces a random multiplier. This produces a curve:
Difficulty ramps over 180 seconds. Spawn interval goes 2.0s → 0.6s linearly.
Enemy speed goes 100 → 160 over the same window. At t=120s introduce the
second enemy type. Nothing changes after t=180s; the game should be
effectively over by then.
The t=180s ceiling matters more than the numbers. Generated games tend to be endless, and endless games have no arc. Give the session a length and the difficulty curve has somewhere to go.
3. There is no fail state, or a fail state that cannot be reached
What the player says: "I don't know if I'm winning."
Why it happens: two opposite defaults, both common. Either the model adds a health value that never depletes because no damage source was specified, or it adds a fail state so harsh that the first mistake ends the run in four seconds. Both read as "no game."
The fix: specify both ends and the margin between them.
Player has 3 lives. Contact with lava costs 1 life and grants 1.5s of
invulnerability. Falling off the bottom costs 1 life and respawns at the
last checkpoint. Losing all 3 shows a results screen with distance
travelled and a retry button. Reaching 500m shows a win screen.
The invulnerability window is the part people forget, and it is what separates "hard" from "unfair." Without it, a single overlap frame eats every life at once and the player experiences a bug rather than a loss.
4. Movement is frame-dependent, so it feels different on every device
What the player says: "It's floaty on my phone but fine on my laptop."
Why it happens: the generated update loop adds a fixed amount per frame instead of scaling by delta time. At 60fps it's correct. At 120fps the character moves twice as fast; on a throttled phone it crawls. This one is invisible in the editor and obvious to every player.
The fix: one instruction, applied globally.
Multiply every position, velocity, and timer update by delta time
(seconds since last frame). Clamp delta to a maximum of 1/30s so a
dropped frame cannot teleport anything through a wall.
The clamp is not optional. Delta-time scaling without a clamp turns every stutter into a tunnelling bug, and you will spend an hour chasing a collision problem that is actually a timing problem.
5. Collision is generated per sprite instead of tuned per game
What the player says: "I fall through the floor sometimes."
Why it happens: the model sizes hitboxes from the sprite's bounding box. A fox with a tail and ears gets a hitbox that includes empty pixels, so it snags on ledges and floats above ground. Fast-moving objects skip thin platforms entirely because collision is checked once per frame.
The fix: decouple the visual from the collider and add a swept check.
Use a capsule collider for the player, inset 20% from the sprite bounds,
positioned at the feet. For any object moving faster than 400px/s, check
collision along the path between last and current position, not just at
the current position.
Inset colliders are the standard fix for "my character floats." Swept collision is the standard fix for "I pass through platforms when I'm fast." They are different bugs with different causes and generated code usually has both.
6. State sprawls, so every new feature breaks an old one
What the player says: "It worked before you added the score."
Why it happens: generated code adds each feature as its own global variable with its own update block. After eight features you have eight blocks all writing to the same player object in an undefined order. Adding the ninth reorders the writes and something downstream breaks. This is the failure that makes people conclude AI can't build a real game — it can, it just can't maintain one it built without structure.
The fix: impose the structure before you add features, not after.
Refactor so there is one game state object with explicit fields
(player, enemies, score, phase). All updates read from and write to that
object. No feature may declare its own globals. Phase transitions
(menu → playing → dead → results) are handled in one place.
This is a one-time cost of an hour that buys you the rest of the project. If you skip it, the cost compounds at every feature and you will abandon the build around feature eleven.
7. Saves are written non-atomically, so progress vanishes
What the player says: "I lost everything when the app closed."
Why it happens: the generated save writes directly to the storage key. If the app is backgrounded mid-write, the key holds a truncated value, the parse throws on next launch, and the catch block silently resets to defaults. On mobile this happens constantly and looks like data loss with no error.
The fix: write to a temp key, verify, then swap.
Save to `${key}_tmp`, read it back and parse to confirm it is valid,
then write to `${key}` and delete the temp. On load, if `${key}` fails
to parse, try `${key}_tmp` before falling back to defaults. Include a
schema version field and migrate old saves instead of discarding them.
The version field is what stops the second, worse version of this bug: shipping an update that changes the save shape and wipes every existing player.
What this list is really saying
Every one of these seven is an unspecified slot. The model is not failing at implementation — it is filling gaps with defaults, and the defaults are chosen to be safe rather than good. A prompt that specifies the loop, the two end states, the escalation numbers, and the timing model removes six of the seven before the first generation runs.
The practical version: write your first prompt as a spec with named slots, not as a description. Then fix in the order above, because the first three fixes are free, and a game with feedback, a curve, and a reachable fail state already feels like a game — even if the code underneath is still the same generated mess.
The remaining mess is worth fixing too, but it is an engineering problem. It is not why the build felt broken.