Designing the loop, not the prompts

•6 min read•#claude-code#ios#game-dev#automation

I build an iOS arcade game called T-Minus Zero: Thrust (hold to thrust, land softly).

It's a solo project, and a lot of the code is written by Claude Code.

The part I've learned the most from isn't the prompting.

It's the loop around it: what runs unattended, what it is forbidden to do, and where I step in.

The loop: a circular task cycle, then a release path


Two ways work gets in

My main features still come from prompting. I describe what I want, work through it in a session, and push.

Improvements go through the loop.

These are the many small things a game needs: new missions, tuning, polish, accessibility passes, tests.

I run a routine called run-task-cycle for a few cycles, and it does three things on repeat:

  1. Write a task. A skill picks the next idea from my roadmap, or a small improvement it finds in the code, and writes a task file to a fixed template: goal, background, requirements, out of scope, acceptance criteria.
  2. File an issue. Another skill turns that file into a GitHub issue in a To Do milestone.
  3. Build it. A third skill works the open issues oldest first. It makes the change, runs the check scripts, commits and pushes to dev, then moves the issue to For Review and closes it.

When the queue is empty, the loop is done. Then it runs once:

  1. Push to github-ci. The routine fast-forwards dev into the branch that Xcode Cloud watches. It never force-pushes.
  2. Xcode Cloud builds it and sends a TestFlight version.
  3. I play it on my phone, and what I notice becomes the next round's tasks.

Features I prompt by hand skip steps 1 to 3 and join at step 4.

Both routes end in the same place: a build on my phone.


The loop is the easy part

Chaining skills together took an afternoon.

What lets me run it without watching is what I forbade it from doing.

Four rules do most of the work.


1. Only bug fixes reach Release.

Every other change - whether a feature, tuning, content or copy - must sit behind #if DEBUG or a flag that is false in Release, until I promote it in a separate task.

The loop can ship all day without changing what the App Store build does.

This is the rule I'd copy first.


2. Park, don't guess.

If an issue is large, has an unresolved open question, touches a protected area (the landing physics, the in-app purchases, the leaderboard IDs) or needs a manual step, the loop moves it to a New milestone with a comment saying why.

It never improvises on the parts of the project I'd hate to get wrong.


3. Checks report, never edit.

The repo has 43 small check scripts: UK English and no dashes in copy, every shown character exists in the font, product IDs match the code, Debug-only switches are off in Release, docs match code.

They only report.

The loop can't pass a check by editing the check.


4. Generate only when the queue is empty.

The task-writing skill refuses to run if To Do already has issues.

Work can't pile up faster than it ships.


All of this lives in written rules, not in anyone's memory.

A CLAUDE.md file holds the business rules, the vocabulary and the commit format, and the skills are told it wins over whatever an issue says.


It runs while I sleep

The loop is designed to run in a cloud environment.

That was a deliberate decision, not an afterthought.

It means the loop does not need my machine, my network connection, or me to be awake.

I can kick off a run before bed. By morning, there are commits on dev, issues moved to For Review, and a TestFlight build on the way.

The rules that make this safe are the same ones above. A loop that runs unattended on my laptop is one thing. A loop that runs while I am genuinely not there has to be more careful about what it is allowed to touch.

The cloud environment is also stateless. There is no half-open file, no half-remembered context from the last session. Each run starts clean, reads from the repo and the issue tracker, and does exactly what the skills say.

That constraint turned out to be a feature. It forced me to write everything down.


The phone is the real test

The loop can build a mission. It can't tell me whether the mission is fun.

Game balance numbers are marked in my own notes as "playtest this", and the loop respects that: it will add the content, but only my hands on a TestFlight build can judge it.

It's also honest about what it didn't verify.

It can't run the unit tests in a simulator, so its reports say "build and tests not run" rather than claiming they passed.

I run the full suite from Xcode, and the final check is the build on my phone.

I'm the verification step at the end of the loop, and I think that's the right place for a person to be.


What went wrong

  • No release tag. I never tagged releases, so writing release notes meant working out the last shipped version from bump commits and dates. That annoyed me enough to turn it into its own skill.
  • Release notes in developer wording. Drafting notes from commit subjects gives text players can't read. They need rewriting for players.
  • Saved progress. Renaming or removing a mission can silently reset players' progress, because the saved key is built from the mission's title. The fix was a separate display name, and the rule is now written down so the loop respects it.

The numbers

Over roughly a year of history there are about 1,000 commits and 212 tasks filed as issues.

Most of that came in the last few weeks, once the loop existed.


What's still left to do

The loop works, but it is nowhere near finished.

A few things I want to add:

Self-healing. Right now, if a build breaks, the loop stops. A better version would catch the failure, read the error, fix it, and keep going - without waking me up. The check scripts already report exactly what went wrong. The next step is letting the loop act on that report.

Agent-based verification. The loop currently skips simulator tests because it can't run them in a cloud environment. What I want is a separate agent that takes the finished build, installs it, exercises the key paths, and files an issue if something looks wrong. Verification by agent, not by assumption.

Smarter task generation. Right now the task-writing skill picks from a roadmap file or spots things in the code. It could also look at crash reports, user feedback, or TestFlight comments and generate tasks from those. Closing the feedback loop automatically.

Tighter scope per cycle. Some cycles try to do too much. I want the loop to estimate the size of a task before it starts and skip anything too large rather than parking it mid-way through.

None of these are hard in isolation. The interesting part is adding them without making the loop harder to trust.


What I'd tell someone building their own

  • Design the stop conditions before the prompts. Decide what the loop must refuse to touch and write it down.
  • Make the safe path the default. Debug-only by default means a mistake costs a TestFlight build, not an App Store release.
  • Keep a person at the end of the loop, holding the real device.
  • Let the loop do the improvements, and keep the features for yourself.