Day 2: It Runs on an iPhone

From a janky SDL window to a working iPhone prototype in a single day. Plus the first warning sign that the 'no tech gap' feeling has a catch.

Eyal Harush6 min read

Yesterday I found a vertical climber written in C++, got Claude to fix its broken build, and watched it run in a tiny SDL window on my Mac. By the time I went to bed, it was clear this could actually become something. The question was what.

The car conversation from day 1 was specific: "iOS arcade game." Not "a game I can show my friends on a laptop." The whole point was to put something on a phone. So day 2 had a very concrete goal: get this thing running on an iPhone.

I have never written an iOS app. I have never written Swift. I have never opened Xcode beyond the occasional reluctant glance when a build system asked me to. My day job is Node and TypeScript and React, with Fastify on the backend and Postgres underneath. That's the entire surface area I know.

I had Claude.

The architecture decision

Before touching any code, I had a long conversation with Claude Opus about how to approach this. Three options on the table:

  1. Rewrite the game in native Swift. Cleanest option, native performance, all-Apple stack. But the C++ physics had been tuned by the original author over months, and I wanted to preserve the feel exactly. Rewriting by hand meant risking subtle drift in the exact numbers that make the game feel right.

  2. Cross-platform framework (Flutter, React Native, Kotlin Multiplatform), something that gives me Android for free. Felt wrong for a game. Performance matters. Native UI matters. And this was literally day 2 of ever touching game dev. Android was a distraction I couldn't afford.

  3. Unity. Way too much for day 2. Learning Unity would eat the day. Opening the editor would feel like a bigger project than the one I was trying to do.

Opus suggested a fourth option I hadn't considered. Keep the C++ engine as the runtime brain. Wrap it in a Swift shell via an Objective-C++ bridge. Physics untouched. Swift for UI, input, and system integration. The bridge shuffles state between them.

On paper: the best of both worlds. I didn't know Objective-C. I didn't know Swift. I had no idea what a bridging header was. None of this was a problem because I didn't need to write the bridge myself. Claude would.

Pull the trigger

I pulled the trigger at 16:56 with a massive first commit:

Ship AyaEscape iOS prototype updates (#1) — 227 files

Two hundred and twenty-seven files. Most of them generated by Xcode (the project structure is verbose at its best and absurd at its worst), but buried among them was a working iOS app wrapping the C++ game engine. The physics ran. The camera worked. Touch input was wired up to the C++ input handler via the bridge.

By 22:45, same day, there were eight more commits:

Normalize Xcode project metadata Add repo-wide code owners Checkpoint settings and control panel pass Checkpoint difficulty chase tuning Reset session when returning to main menu Merge pull request #2 from theharush/codex/difficulty-chase-tuning Implement bunker platform themes and collision fixes Add remaining level design source assets

Touch joystick. Settings panel. Difficulty tuning. Bunker platform art (remember, the game was called AyaEscape at this point, with a satirical viral concept I'll explain in a future post). Collision fixes. All in a single day. From "it runs on my Mac" to "it runs on my phone with real UI."

I couldn't stop coming up with ideas. Every feature felt reachable. Every problem felt solvable. The feeling I had going to bed on day 1, "this could actually become a game," was by day 2 replaced with something bigger: there is no more tech gap.

The caveat

Except there is.

Here's what I noticed toward the end of day 2. Somewhere in those nine commits, ContentView.swift grew to roughly 2,500 lines and contained most of the game's Swift-side logic. All the game states, all the UI, all the input handling, all the settings plumbing. One file.

Claude had been happily adding features to this file without refactoring it. And as the file grew, something subtle started happening: Claude would stop seeing parts of it. I'd ask for a change to the settings panel and Claude would generate new code instead of modifying the existing settings code that was already there, 1,800 lines up. I'd ask to fix a bug in the game-over flow and the model would write a near-duplicate game-over handler because the original was buried too deep in the file for it to find cleanly.

The pattern isn't "Claude can't read 2,500 lines." It can. The pattern is that when a file is that large and unstructured, even a strong model starts pattern-matching instead of navigating. It sees "there's a game-over thing I need to add" and generates one without thoroughly checking whether a game-over thing already exists three screens up.

The fix is what you'd think: define enums for the game states, extract common UI components, pull out repeatable patterns, split the file. Architecture hygiene. The kind of thing a good senior developer does reflexively.

This is the thing nobody tells you about AI-assisted development. The tech gap doesn't disappear. It shifts. You don't need to know Swift. You do need to notice when the thing Claude is building is starting to eat itself, and intervene with the right intuitions about architecture and decomposition. Those intuitions are transferable across languages. They're the same intuitions a web dev uses when a React component grows too big. I have those. I didn't need to know Swift to know the 2,500-line file was a smell.

The tech gap doesn't disappear. It shifts from "can you write code" to "can you architect and guide." Every subsequent post in this series will have a moment where this pattern shows up in a new form.

Still one terminal, still "AyaEscape"

One more thing about day 2. Every single one of those nine commits happened in a single Claude Code session, in one terminal window, with no plugins, no custom skills, no parallel worktrees, no MCP integrations. Just Claude Code out of the box, one chat, one context window. The project was called "AyaEscape." The character was named "Aya." Neither will survive past the rebrand day later in the series.

I didn't have CLAUDE.md yet. I didn't have the /ship skill. I didn't have subagent-driven development. I didn't have any of the workflow machinery I now rely on. I just typed at Claude and Claude typed back and somehow by end of day 2 I had a playable iPhone prototype.

The sophistication of the workflow grows over the course of the series. Day two is the baseline: scrappy, unstructured, direct. It's useful to remember how primitive the setup was when the feeling "there is no more tech gap" first hit. Because what I mistook for the tool being magical was actually the tool being sufficient. The magic was how much a programmer can do with just "sufficient" if you're willing to push it hard and stay awake for the parts that aren't obvious.

This is post 2 of 18 in a series about building Geo Climber with Claude Code. The iPhone port worked. The architecture was wrong. Join the Discord and download Geo Climber on the App Store.

iosswiftobjective-c++architectureprototypetech-gap-shiftsclaude-code
Day 2: It Runs on an iPhone — Building Geo Climber