This post is about rediscovering my own project, using it to ship an app, and what I learned along the way.
Few people know, but I have recently had my fourth child. This time around I took advantage of the 12-weeks of parental leave offered at my job.
I had so much time to be a dad to a little one again. I loved every minute. I also loved the time I got back in my own mind when it was not occupied by the plans, problems, and fires to put out at work. I spent almost all of my parental downtime — time when I was not with the baby, the kids, the dog — either working on Codea, or out in nature, running and walking (dragging the family with me, when possible). This didn’t amount to much, but when your house and family is this full, every single minute to yourself is treasured.
During my downtime we released six updates to Codea, including big ones like Code Notes. Codea, for those unfamiliar, is our iOS coding environment for building games and simulations in Lua. It has a unique style and simplicity about it that has made it popular.
Working on Codea excites the designer and engineer in me. It is full of all sorts of technologies from a custom code editor, to multiple render engines (one OpenGL, one Metal), to language parsing and abstract syntax trees, and lots of iOS-native UI. The project itself spans many different languages. Working on Codea is fun because there are hard problems: can I infer these types in Lua? How do I design a rendering API that is easy to learn for new users, but still powerful? How can we build a touch interface that scales from small to large coding projects?
I’ve been working on Codea since 2011.
But I haven’t used it nearly enough. As the last week of my leave approached, I made a promise to myself: by the end of the week I would ship an app that I would make using my own app.
I decided I wanted to make a simulation of the “scary numbers” from the TV show Severance. The retro-computing graphical interface paired well with Codea’s default direct rendering mode, and a CRT shader would give the right vibe. It’d be fun.

The second thing I decided was that people who used this app should be able to open the project into Codea directly from the app and modify it. This way the project wasn’t just dogfooding Codea, it was also a way to spread the word.
Monday March 31, 2025
I put the little one to sleep and went to my iPad instead of my laptop. I created a new Codea project in iCloud called “Terminal of Woe.”

Getting a grid of numbers up onto the screen was easy. I decided to render my game to a texture instead of to the screen with the intent to apply shaders to it later, and the simplicity of doing this in Codea made me smile.
setContext(p.screen)
background(30, 48, 63)
fill(207, 226, 229)
for i,row in pairs(p.data) do
for j,val in pairs(row) do
fontSize(11)
text(tostring(val), j * 33, i * 33 + 10)
end
end
setContext()
sprite(p.screen, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)

My thoughts were: wow, it just shows up when I hit play. It doesn’t compile. Not worrying about how to engineer this is very relaxing.
I spent some time tweaking numbers and just watching results update in real-time. Adjusting colours and making aesthetic choices was a delight.
This was followed by: but why is Codea crashing every now and then when I select text and hit “Copy” in the UI?
This pulled me back into reality, and onto my laptop, where I saw an email and a forum post from users asking the same question.
My initial venture into dogfooding was now sidetracked by a bug. I briefly considered plowing ahead with the project as if I were a regular Codea user, but couldn’t help myself. Back on my laptop, I discovered the culprit was our move, in the previous release, from the deprecated iOS UIMenuController
for showing interactive text UI, to the new UIEditingInteraction
replacement. This had been a fairly simple swap, but for some reason the cut:
/ copy:
and paste:
selectors were occasionally sent to our higher-level code editor container, rather than the code editor itself (most of the time it worked correctly). It was easy enough to forward these messages, but I couldn’t come up with a way to deterministically reproduce this and didn’t have the source to iOS to figure it out from that end.
So Codea 3.13.4 was submitted to the App Store instead.
Tuesday & Wednesday April 1–2, 2025
With the crash fixed, I focused on implementing the basic simulation. Now that I had a grid of numbers that looked vaguely like the terminal from the TV show, I thought, what would I do if I was just using Codea to get something out? I’d try vibe coding.
I asked Claude,
Can you take my grid of numbers, and make them jitter slightly when they come within a certain radius of my touch?
(The actual prompt included quite a bit more detail, mentioned “retro computer terminal,” and included my code so far.)
The response from Claude included the whole CRT shader. Something I had not asked for, but it worked extremely well. As I wasn’t yet ready to develop the CRT shader, I put that aside to work on later and tried to refocus the efforts on getting Claude to make my numbers move in mysterious and important ways.
No matter how much I tried I couldn’t get a decent result out of the LLM for making the meat of my project. As the project started to get larger, the LLM output seemed to get worse. Asking it to implement the various bits of interaction: capturing numbers, making them swell according to a noise grid, just resulted in a mess. Attempting to refine through further instruction often left me with the LLM repeating past mistakes and then correcting those mistakes in a bit of a loop. The mistakes weren’t ones I could easily correct, they were just fundamentally the wrong approach and produced the wrong output.
I decided to ditch the vibe code and made a mental note to come back to Claude when I needed help with the CRT shader.
Through all of this, whenever I pasted Claude’s code into Codea, I noticed that the multi-line string it had embedded its GLSL code inside of caused indentation errors in Codea, but would fix themselves shortly after a code edit. This drove me nuts, to the point where I had to attach the debugger to Codea once again to see what was happening. It turned out that the architecture I had built many years ago for adding smarts to the code editor (a series of “semantic builders” that run over the code in a background thread) require a dependency graph — that is, there should have been dependencies between the modules that evaluate the code for the purposes of things like indentation. A feature branch, feature/ast-based-indentation
was born, but left for after shipping Terminal of Woe.
Thursday April 3, 2025
Having re-written most of the vibe code from the previous days, I had a main screen with capturing mechanics. I was pretty pleased with how the code was looking. I had even attempted to bring some of my favourite Swift features into Lua — I had written a State
class that behaved a bit like Swift enums with associated data. Of course, mine were a crude approximation, but they let me flip between states in my game and attach arbitrary data to those states in a way that was reminiscent of how I would do it in Swift.
At this point I notice that working with a dynamic language is very cool when a project is on the small side, but being allowed to run essentially any code so long as it is syntactically correct has me doubling-back a lot more because of typos in variable names. I desperately want to stop work to add a feature to Codea to highlight variables which have not been assigned. I ignored the urge.
While my vibe coding from the previous days did not work as hoped, the CRT shader showed promise. I asked Claude to generate the shader once more and to make the effect “crunchy” and high-contrast. It delivered.
Instead of embedding this in a multi-line string, as Claude had been doing so far, I decided to use the Shader Lab feature in Codea to write it as a dedicated shader asset that I could load into my code with the shader()
function. This is an old area of Codea, so I was a little nervous of facing it as a user, rather than as a developer — where I could fix any of the problems I found along the way. It turned out, though, that the Shader Lab was pretty good and I was able to paste Claude’s fragment shader in pretty much unmodified, and get a real time preview with interactive parameters.
The Codea-developer side of me desperately wants to come back soon to modernise Shader Lab for the Metal-based render engine. I also wanted to re-use that real-time preview window and put it in Codea proper.
Friday April 4, 2025
The kids have figured out what I am up to. They love Severance and have been fantastic beta testers. I Air Drop the project to their iPads and they tell me everything that’s wrong with it.
I’ve come to appreciate iCloud for coding projects. It’s such a strange feeling to be coding on my iPad, then pull out my iPhone and have the changes right there, in my hand. No git pull
required. Although I feel like I am flying blind without git and have become paranoid that I will lose my changes. I start to design a “Snapshot” feature for Codea in my head, something that’s not git
but allows people to code with more confidence than simple autosave.
It is at this point that my project is getting large. I decide to use Air Code. This is a feature of Codea lets you edit code live on your device from VS Code on your desktop. This is one of the features of Codea that I haven’t had much hand in building lately — JF from our team has led the initiative. This time, I really do feel like an end user.
I open my iPad next to my laptop, running Codea.
In VS Code and hit ⌘⇧P to bring up the command palette. I enter “Codea: connect to host” — my iPad shows up in the list.

I select it and see my iCloud projects, my local projects, everything. I tell JF that this is freaking magical.

The feature continues to impress me. I hit play in VS Code and my iPad plays the project. I do the large-scale refactoring of my project that I have been dying to do but have found slightly too cumbersome on my iPad. I am spoiled by my large display and the ability to move big chunks of code around with ease.
I quickly refactor my code to support multiple screens. Up until this point, it has felt like a prototype where hitting “play” would dump me into the game. Now I have title screen, info screen, game screen and the ability to restart and navigate between screens. I relish how simple this is to do with Lua, so long as I am disciplined, as there is no compiler to tell me off.
At some point I need a pixel art goat. I try my hand at animating one using Pixaki on iPad. It exports to a sprite sheet, which is really nice.

When I drag-and-drop the image asset into VS Code it is automatically copied into the project on my iPad, and immediately useable by Codea. I am again in awe of the work JF has done in making Air Code as good as it is.
A real test has come when my littlest one wakes from his sleep once again. Sometimes, after getting him back to sleep at night, I have been able to get my laptop or iPad on my lap to get some work done while he sleeps on me. This time I am wanting to continue to use Air Code, but it’s way too hard to have an iPad and a laptop and a sleeping baby on me.
I have my iPhone, however. So I place my iPhone off to the side and Air Code into Codea from my laptop, sleeping baby undisturbed. It’s 11 PM or so and within 30 minutes I have ported my iPad-only screen layouts to also support iPhone, adding compact and regular display modes to the application.

The whole Air Code experience has left me feeling good. I’ve still been editing on my iPad and iPhone, but that has now been smaller changes in direct response to either using the game myself, or to my kids playing it. The large scale refactoring was more pleasant on the desktop, and I wonder that night if there is a good touch-first design for making sweeping changes to a codebase.
Saturday April 5, 2025
It’s the weekend so I am out most of the day with the kids. On a walk, my oldest son tells me I should include “Complaints” in the app so that people can lodge complaints. Just like what happens in the show. The idea sticks in my mind and ends up becoming one of my favourite bits of the app.
I don’t get much time to work on the terminal today, as my son has asked that his friend sleep over (to which I will always reply yes — more people, more chaos). Though I have to cook dinner for everyone, take the dog for a walk, take a subset of kids to a birthday party, and so on.
In the end I get around 45 minutes that evening of cobbled together time to implement the Grievances screen.

Selecting any option just says “Grievance Submitted” — but it’s at this point I decide to dive into Codea’s Objective-C bindings. (Also a feature built by JF!)
I decide that selecting any of the first four options should try to request a rating on the App Store using objc.SKStoreReviewController
. The code is very simple:
local scene = objc.app.keyWindow.windowScene
local store = objc.SKStoreReviewController
store:requestReviewInScene_(scene)
The last option, initiate freeform lamentation, goes to the “Write a Review” page for the app on the App Store. This is as simple as calling openURL()
with the right URL for my app.
I tell my oldest son that I’ve implemented his idea. His response is a very mild, “cool.” He’s far less interested when he has his friends around.
The game is mostly done at this point.
Sunday April 6, 2025
I feel confident that I can meet my deadline. It is unlikely that I will get to work on the App Store submission until 10 PM or so, when all the kids are asleep, but now all I need to do is ship it, right?
The first thing I do is select my project in Codea and Export to Xcode. The resulting project works — but the custom font I am using is not showing up. I add it manually to my Xcode project and make an entry in the app’s Info.plist
. It’s all working pretty well.
I do some test builds so I can try out the “Import into Codea” feature of the app. It works as expected. But then I realise that other users of Codea will not have the custom font I am using installed on their iPads. I resolve to add a way for Codea to load custom fonts at runtime in the future, but until then, I submit update number six to Codea which embeds this particular font, just so people who eventually download Terminal of Woe have a smooth import into Codea.

It ends up taking me until 3 AM to write the various bits of copy, create screenshots for all required devices, and fix last-minute bugs. The process has been smoother than expected though. And I go to sleep dreading awaiting a response from App Review.
Monday – Wednesday April 7 – 9, 2025
They responded alright.
Only three rejections later and it’s on the store! Given that I’m back at work, and App Review mostly functions at night (because I’m in Australia), it’s hard to find time to sleep. Here’s an overview of the rejections.
Rejection 1: this is a spam app which uses an app template creator of some sort
My understanding of this rejection was that the screen above was seen by App Review and perhaps Codea was thought to be some sort of cheap app producing framework or tool. I explain that Codea is more like Unity — a game engine, and many games show a “Made with Unity” logo, and this is akin to that aside from the fact that Codea also runs on iOS.
Rejection 2: where is the AR functionality?
This took some thinking, but I realised the Codea runtime that gets linked with exported projects includes ARKit, as Codea has AR functionality. But it was unused in my app. I decided it was best to remove the ARKit symbols from the public Codea libraries as they are unlikely to be used. In the event that someone wants to export an AR-supporting app from Codea onto the App Store, I would be happy to provide support (rather than the other way around). Resubmitted with no ARKit-related symbols in the binary.
Rejection 3: how do we play this?
This one was fair. The app was inscrutable and mysterious, but to fans of the show it was obvious. Providing a video play-through, which I should have done from the start, satisfied App Review.
And then it appeared on the store.