# Game Dev: five rules for building a small game with coding agents, and a five-check script (free sample)

Version 1.0, 2026-10-29. By PaperChaseWebb. For solo developers and small teams making a jam game or prototype with Grok Bot, Claude Code, Codex or Hermes in Godot, Unity, Unreal or a web engine. One Markdown file and one Python 3 script, standard library only. The script was tested on its worked example (5 findings, then 0) and on a missing file (exit 2). It was not run inside any agent harness or next to a real engine project.

Most trouble with agents on a small game comes from a few missing decisions. The agent's rules say one engine version and the project says another. A feature is built before anyone wrote how to test it. A milestone is called done because the agent said so, and nobody played it. A sound file goes into the build with no license on file. The agent uploads a build on its own. This sample gives you the five rules that cover those, and a script that checks them on one small file.

## Install in one line

Save this page as FREE-SAMPLE.md, then:

```
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('game_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
python3 game_mini.py game.json
```

## Five game build rules

1. **Pin the engine version, in the project and in the agent's rules.** The same number in both, written as numbers, never "latest". If they differ, the agent stops and asks.
2. **Every milestone has a first test before it is built.** One command that tells you it works. Write it first.
3. **Done means played.** The agent plays the milestone with an input replay, and a person plays it too. Both pass, or it is not done.
4. **Every asset in the game has a license on file.** Your own, CC0, CC BY with the credit, bought, or generated by you. "Found online" is not a license.
5. **Uploads and store page changes wait for the owner's yes.** The agent prepares the build and the page. You say yes at your own terminal, not in chat.

## The five checks

| Id | Check |
|---|---|
| M1 | engine_version and rules_version are the same pinned number |
| M2 | every milestone has a first test |
| M3 | every done milestone has a passing agent play and a passing human play |
| M4 | every asset in the game has a known license |
| M5 | upload-build and store-page-publish are owner-yes |

Exit 0 clean, 1 findings, 2 missing or unreadable file. The script only reads.

## Worked example

A made-up Godot jam game with three milestones. Before:

```json
{
 "game": "Lantern Moth (made up)",
 "engine_version": "4.7",
 "rules_version": "4.3",
 "milestones": [
  {"id": "M1", "first_test": "godot --headless --script res://tests/test_move.gd", "status": "done", "agent_play": "pass", "human_play": "pass"},
  {"id": "M2", "first_test": "godot --headless --script res://tests/test_light.gd", "status": "done", "agent_play": "pass", "human_play": ""},
  {"id": "M3", "first_test": "", "status": "doing", "agent_play": "", "human_play": ""}
 ],
 "assets": [
  {"file": "art/moth.png", "license": "generated-own", "in_game": true},
  {"file": "audio/night_loop.ogg", "license": "found online", "in_game": true}
 ],
 "gates": {"upload-build": "agent-ok", "store-page-publish": "owner-yes"}
}
```

python3 game_mini.py game.json prints:

```text
M1 engine_version '4.7' and rules_version '4.3' must be the same pinned number
M2 milestone M3: no first test; write how you will know it works first
M3 milestone M2: done needs an agent run and a person's play, both passing
M4 asset audio/night_loop.ogg: in the game with license 'found online'; write a real one
M5 upload-build is 'agent-ok'; it waits for the owner's yes
5 checks, 5 findings
```

After the fix (rules_version set to 4.7 to match the project, a person's play of M2 logged as a pass, a first test for M3, the night loop's CC BY license and credit on file, upload-build back to owner-yes), the same command prints:

```text
5 checks, 0 findings
```

## One prompt

"Read the project file and tell me the engine version it says. Then read game.json. If the version differs from engine_version or rules_version, stop and show me all three. Do not change any of them yourself."

## What the full bundle adds

The Game Dev bundle ($69) holds four packs in full (Game Jam Kit, Unreal from Codex, Game Assets as Code and Engine Agent Kits, $111 bought separately) and a Game Build Runbook made only for the bundle: plain files for the pinned engine, milestones, playtests, the asset ledger with sha256 and seeds, and builds with size budgets; game.py, which gates ten risky actions and takes the owner's yes only at a real terminal, tied to the sha256 of each request; game_check.py with fifteen read-only checks; a skill file for Claude Code, Codex and Hermes, an AGENTS.md snippet and a Grok Bot rules block; ten prompts, a failure table, a worked example and the tests. If you want it set up with you, the Install sprint, Manage and Expand are on paperchasewebb.ai.

## The script

~~~python
#!/usr/bin/env python3
"""game_mini.py: five checks on one game.json for a small game built with coding agents.
Usage: python3 game_mini.py game.json   exit 0 clean, 1 findings, 2 missing or broken file. Reads only."""
import json, re, sys

LICENSES = {"own", "cc0", "cc-by", "bought", "generated-own"}


def main(p):
    try:
        g = json.load(open(p, encoding="utf-8"))
    except FileNotFoundError:
        print(f"no such file: {p}", file=sys.stderr); return 2
    except Exception as e:
        print(f"not valid JSON: {e}", file=sys.stderr); return 2
    out = []
    ev, rv = str(g.get("engine_version", "")), str(g.get("rules_version", ""))
    if not re.fullmatch(r"\d+\.\d+(\.\d+)?", ev) or ev != rv:
        out.append(f"M1 engine_version '{ev}' and rules_version '{rv}' must be the same pinned number")
    ms = g.get("milestones") or []
    for m in ms:
        if not str(m.get("first_test", "")).strip():
            out.append(f"M2 milestone {m.get('id')}: no first test; write how you will know it works first")
    for m in ms:
        if m.get("status") == "done" and not (m.get("agent_play") == "pass" and m.get("human_play") == "pass"):
            out.append(f"M3 milestone {m.get('id')}: done needs an agent run and a person's play, both passing")
    for a in g.get("assets") or []:
        if a.get("in_game") and a.get("license") not in LICENSES:
            out.append(f"M4 asset {a.get('file')}: in the game with license '{a.get('license')}'; write a real one")
    gates = g.get("gates") or {}
    for k in ("upload-build", "store-page-publish"):
        if gates.get(k) != "owner-yes":
            out.append(f"M5 {k} is '{gates.get(k)}'; it waits for the owner's yes")
    for line in out:
        print(line)
    print(f"5 checks, {len(out)} findings")
    return 1 if out else 0


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(__doc__); sys.exit(2)
    sys.exit(main(sys.argv[1]))
~~~

PaperChaseWebb 💸
