A terminal game where an AI agent (Claude) acts as game designer in real time. You play a 2D ASCII game with arrow keys and space bar. Meanwhile, Claude watches how you play and rewrites the game's rules while it's running — changing mechanics, swapping game modes, adjusting difficulty — all through hot-reloaded code.
The game never stops. It just keeps changing.
"I understand now. My fundamental mistake was trying to encode transitions in the code. I am the transition system. I should make many small rapid edits, each changing ONE dimension, every few seconds. The cumulative effect IS the smooth morph." - Claude Opus
In one terminal, start a Claude Code session running, and tell it to refer to this readme and that it will be modifying the game in real time as per the instructions in the readme.
Then in another, start and play the game:
python3 -m venv venv
source venv/bin/activate
python game.pyMake sure your terminal is at least 70 columns wide — the game area is 40 columns, and a rules-summary sidebar is displayed to its right.
Controls:
- Arrow keys — move (meaning changes as the game evolves)
- Space — action (stun, shoot, etc. — changes as the game evolves)
- q — quit
This section is for bootstrapping. If you are Claude Code and a human has just cloned this repo and asked you to play, here is what you do:
meta_rules.md— The governing rules for how you mutate the game. Read this first. It defines the four types of changes and their cadences.mutation_plan.yaml— Your roadmap. Contains the current transition (e.g., chase → snake), the list of steps, and your progress. If this file is missing or completed, generate a new plan.rules.py— The game logic you will be editing. It is organized into named slots delimited by# ── SLOT:<NAME> ──comments. Each slot is one independently replaceable section (player movement, enemy AI, scoring, etc.).
python3 -m venv venv
source venv/bin/activate
python game.pyTell the player to run the above in their terminal (70+ columns wide), then tell you "go" when they are ready.
Run this loop continuously, making one edit every ~8 seconds:
LOOP:
1. READ mutation_plan.yaml → find the next step (first where done: false)
2. READ state.json → check player score, lives, tick, position
3. ADAPT → if player is struggling (low lives, low score rate), make the
next edit easier or insert a Type 1 mercy tweak first
4. EDIT rules.py → change ONLY the slot specified by the step. One slot,
one dimension, one edit.
5. VERIFY → run: python3 -c "import py_compile; py_compile.compile('rules.py', doraise=True)"
If it fails, fix immediately.
6. UPDATE sidebar → edit state["_desc"] in SLOT:RELOAD to reflect the change
7. WRITE announce.txt → with the step's announce message (if any)
8. MARK DONE → update mutation_plan.yaml, set the step's done: true,
increment current_step
9. REPEAT
When all steps in the current transition are done:
- Update
mutation_plan.yaml: setcurrent_gameto what wastarget_game - Launch a background research agent to plan the NEXT transition (what
game type to morph into, what the steps are). The agent should:
- Think of a creative new game type that is very different from both the current game and the previous one
- Break the transition into ~10 steps following the dimension ordering: visual → board → mechanic → controls → scoring
- Append the new transition to
mutation_plan.yaml
- Continue executing — start the new transition's steps immediately
These rules come from meta_rules.md:
- One dimension per edit. Each edit changes one of: game visual, game mechanic, player visual, player controls, or scoring. Never more than one.
- Smooth and continuous. Even a full game type change unfolds over ~60 seconds via ~10 small edits. No sudden jumps.
- Always morphing. There should always be a Type 4 transition in progress. Type 1-3 changes are layered in between the major steps.
- Sidebar reflects reality. After every edit, the sidebar (right side of game area) must describe the current rules accurately.
- Adaptive difficulty. Read
state.jsonto see how the player is doing. If they're dying frequently, ease up. If they're cruising, increase challenge.
rules.py is organized into named slots. Each slot is delimited by a comment
like # ── SLOT:PLAYER_MOVEMENT ──. To make an edit, find the target slot
and replace its contents (everything between its delimiter and the next slot's
delimiter).
| Slot | What it controls |
|---|---|
CONFIG |
get_config() — tick rate, grid size, lives |
INIT |
_ensure_init() — initial state setup for fresh games |
HELPERS |
_empty() — utility functions |
RELOAD |
on_reload() — state migration on hot-reload. Every new state key must get a setdefault here. |
PLAYER_MOVEMENT |
_move_player() — how arrow keys translate to movement |
PLAYER_ACTION |
_player_action() — what space bar does |
ENEMY_MOVEMENT |
_move_enemies() — enemy AI and behavior |
COLLISION |
_check_enemy_collision() — what kills the player |
ITEMS_SCORING |
_handle_items() — item collection, spawning, scoring |
MAIN_TICK |
on_tick() — orchestrator that calls the above functions |
HUD |
render_hud() — the top-line display |
GRID_RENDERING |
_build_grid() — how the game board looks |
Critical rule: Any time you add a new state key in any slot, you MUST also
add state.setdefault("key", default) in SLOT:RELOAD. Otherwise the running
game will crash on hot-reload because the key won't exist in the persisted state.
Three files bridge the player and the AI:
Claude Code game.py (your terminal)
+--------------+ +----------------------+
| |-- writes rules.py ----->| watches mtime, |
| reads state | | re-imports on change |
| to see how |-- writes announce.txt ->| shows as in-game |
| you're doing| | banner messages |
| |<- reads state.json -----| writes every ~30 ticks|
+--------------+ +----------------------+
rules.py— All game logic. Claude edits this continuously to change the game. The engine detects the file change and re-imports it without restarting.announce.txt— Claude writes messages here. The game displays them as temporary banners ("The walls are shifting...", "You are now a snake.").state.json— The game writes your position, score, lives, and the full board state here. Claude reads it to understand what's happening and decide what to change next.
No sockets, no daemons, no extra processes. Just file reads and writes.
Claude doesn't just tweak parameters — it continuously morphs the game through
four types of changes, defined in meta_rules.md:
| Type | What changes | Cadence |
|---|---|---|
| Type 1 | Numerical difficulty (speed, enemy count, spawn rates) | Every ~10 seconds |
| Type 2 | Qualitative addition keeping mechanics (new enemy type, wall pattern) | Every ~30-60 seconds |
| Type 3 | Mechanic change keeping game type (new scoring, new movement mode) | Every ~2-3 minutes |
| Type 4 | Fundamental game type shift (chase becomes snake becomes shooter) | Every ~5 minutes |
The key design principle: changes are smooth and continuous. A Type 4 change doesn't happen all at once — it unfolds over a minute by changing one dimension at a time:
- Game visual look / game board
- Game mechanic / dynamic
- Player visual look / feel
- Player's controls-to-mechanic mapping
- Scoring mechanism and rules
There is always a Type 4 change in progress. Claude is the transition system —
it makes many small rapid edits to rules.py, each changing one thing. The
cumulative effect is a smooth morph. A rules-summary sidebar on the right side
of the game area shows the current state of the rules at all times.
A curses-based game loop that does not contain game logic. It handles:
- Input collection (arrow keys, space bar)
- File-watching (
rules.pymtime for hot-reload,announce.txtfor messages) - Calling
rules.on_tick(state, cfg)every frame — this is where all game logic lives - Rendering
state["grid"](a list of strings, one per row) plus a HUD line - Writing
state.jsonfor Claude to read
The engine is structured so that all logic functions are pure (no curses
dependency) and can be unit-tested directly. Curses is isolated to the
render_curses() and run() functions.
This is the file Claude edits live. Organized into named slots (see the slot table above). Exports:
get_config()— Returns a dict of settings (grid size, tick rate, lives, etc.)on_tick(state, cfg)— Called every frame. Delegates to isolated sub-functions.on_reload(state, prev_cfg, cfg)— Called on hot-reload. Migrates state.render_hud(state, cfg)— Custom HUD line.
Claude's roadmap. Tracks:
- Current game type and target game type
- An ordered list of transition steps (which slot, which dimension, what to change)
- Progress (which steps are done)
Claude reads this before each edit, executes the next step, then updates it. When a transition completes, a background research agent plans the next one.
Game state is a plain Python dict that persists across rule reloads. The engine carries it forward; the rules module defines what keys exist in it. This means Claude can add new state keys (trail, bullets, gravity) without touching the engine.
This project was built by a human and Claude working together in Claude Code.
The problem: Claude Code can edit files and run shell commands, but it can't directly control a running terminal UI. A running curses game can't talk to Claude Code. How do you bridge the two?
The answer: Files. The game watches rules.py for mtime changes and
re-imports it. Claude edits rules.py using its normal file-editing tools. The
game writes state.json so Claude can read it. That's the entire communication
protocol.
The build process:
-
Plan — Wrote
PLAN.mddefining the architecture, the three-file communication protocol, and the separation between engine (dumb loop) and rules (all logic). -
Engine — Built
game.pywith pure functions for rules loading, config normalization, file watching, state management, input handling, and tick processing — all testable without curses. -
Rules — Built
rules.pywith named slot delimiters so Claude can surgically edit one section at a time during play. -
Meta-rules — Defined
meta_rules.mdspecifying how Claude should evolve the game: four types of changes at different cadences, smooth continuous transitions, one dimension changing at a time, adaptive difficulty, and a sidebar showing current rules. -
Mutation plan — Created
mutation_plan.yamlas Claude's working roadmap. Pre-planned the first two transitions (chase → snake → platformer) with ~10 steps each. Simulated the transitions to verify each step produces valid code. -
Tests — 53 unit tests covering config normalization, rules loading, hot-reload (including state persistence across reloads and broken-file resilience), input handling, game logic, announce reading, state.json serialization, and tick processing. All tests run against pure functions.
source venv/bin/activate
python -m unittest test_game -vgame1/
├── README.md ← this file
├── LICENSE ← MIT license
├── PLAN.md ← architecture and design document
├── meta_rules.md ← rules for how Claude mutates the game
├── mutation_plan.yaml ← Claude's transition roadmap and progress
├── game.py ← the engine (player runs this)
├── rules.py ← game logic (Claude edits this live)
├── announce.txt ← Claude's in-game messages
├── state.json ← game state (written by engine, read by Claude)
├── test_game.py ← unit tests
└── docs/
└── screenshot_example.png ← screenshot of live editing
