Downcore is a one-thumb roguelike for iPhone and iPad about falling as deep as you can. It's free on the App Store since September 2.

Categories I'm entering: Best Game, RevenueCat Design Award and Catvertising. Why each one is at the end.

Inspiration

I got properly hooked on Pokelike, a Pokémon roguelike that runs in the browser. It was the classic "one more run" at 1am, and what kept me there wasn't really the Pokémon. It was the loop: a short run, a build that slowly goes off the rails, and dying with a clear idea of what you'd pick next time.

I wanted to make my own game around that loop, but with a different concept, and on my phone. The phone games I liked needed both hands and my full attention, so I only played them at home. The ones I could play standing on a train didn't ask anything of me. Downcore tries to sit in the middle: real decisions, one thumb.

What it does

You steer a small drone down a mine shaft. The drone mines, shoots and picks things up by itself. You drag anywhere to decide where it goes and tap to fire its ability. Your score is how deep you got.

Along the way you pick relics (40 of them), run into events (15, some go well and some go very badly) and fight six bosses across four biomes: the Crust, the Ice Caverns, the Magma Core and the Void, which never ends. Relics stack in ways that get out of hand: a drill that repairs you when it hits ore, ore that explodes, freezing that chains lightning.

The core rule is momentum. If you keep falling at pace, your damage climbs up to x3. Stop behind a rock to be safe and you lose it. Rock is cover, and cover is slow. Most of the game is deciding when that trade is worth it.

There's also a daily run: one seed a day, the same for everybody, one attempt that counts, a streak, and its own Game Center leaderboard. Runs take five to eight minutes. It's in English and Spanish, and works fully offline.

How it makes money

The game is free and nothing is gated: no energy bar, no timers, no depth you have to pay for. The cores you mine buy variety (more relics and events in the pool, abilities), never stats. Drone classes unlock by doing things, like reaching 25,000 m in total, not by paying. So the leaderboards measure skill and not wallets.

There are two ways it earns, and both are optional:

  • Downcore Supporter, a single $4.99 purchase through RevenueCat. No ads, replay the daily seed as many times as you like (replays never count for the leaderboard) and two exclusive drone skins. It's a one-time purchase, not a subscription, and nothing in it makes you stronger. The paywall is a RevenueCat remote paywall, so I can iterate on it without shipping a build.
  • One rewarded ad in the whole app, on the results screen: "watch an ad to double this run's cores". It's capped at three a day. There's nothing inside a run, no interstitials and no banners. When you run out, the button disappears instead of sitting there greyed out. Supporters get the double without watching anything, with the same cap. The ads come from AdMob and are tracked with RevenueCat Ads, so ad revenue and the purchase add up on the same customer.

I left interstitials out on purpose. You can always add them in an update, but you can't take back the one-star reviews from launch week.

How I built it

Swift, with SpriteKit for the run and SwiftUI for everything else. The game logic lives in local Swift packages that never import SpriteKit or UIKit, so the whole simulation runs headless on a Mac. There are 284 tests. The engine runs on a fixed 1/60 s timestep and seeded random streams, which is what makes the daily seed identical on every phone.

There isn't a single balance number in Swift. Relics are JSON written in a small effect language (trigger, condition, effect), and the same goes for enemies, biomes, events and the economy. I balance by editing data and re-running a simulator: a bot that plays thousands of runs headless and spits out a CSV with win rate per relic and causes of death.

There isn't a single image or sound file in Downcore's source code. The app icon is the one exception, and a script draws that too. Every sprite is drawn from shapes at runtime. The menu background is a Canvas that's a pure function of time. The music is five generative tracks synthesized on the device by a small synth I wrote (oscillators, filter, envelopes, delay, reverb, sidechain), and you don't pick the style: it's your depth. The Crust is bright synthwave at 104 bpm, and by the Void it's dark and half-time at 52. All five share a key and a tempo relationship so the crossfades land on the beat.

Everything around the game is generated too:

  • The app icon comes from a CoreGraphics script, not a design tool.
  • The 20 achievement badges and 2 leaderboard images come from another script. Palette means biome, glyph means what it measures, ring means tier. A second tool uploads all the achievements to App Store Connect through the API (localizations in two languages, points, images), so I never typed one in by hand.
  • RevenueCat was set up from the terminal with the RevenueCat MCP server: project, entitlement, product, offering and paywall, and then the product pushed to App Store Connect. Debug builds use RevenueCat's Test Store, so I tested the whole purchase flow in the simulator before the product even existed on Apple's side.
  • The App Store screenshots come out of the game itself. A capture mode drives a live run with the bot until it hits a frame I describe ("boss on screen, health above half, drone away from the walls") and freezes it. Six scenes, two languages, iPhone and iPad: 24 screenshots in about eight minutes, then framed with a headline and uploaded to App Store Connect with fastlane. When the HUD changes, I run it again.
  • The 30-second App Store preview is recorded the same way, and its soundtrack is rendered offline by the same synth, so there's no licensed music anywhere.

I built it pair-programming with Claude Code. The design calls and the playtesting were on me.

Challenges I ran into

  • The first version was boring. The playtest said "it plays itself", which hurt. So I measured it: 97% of a run was waiting for the drill to get through rock. I multiplied mining damage by four, slowed the passive fall, switched cave generation to value noise so the shaft has real caverns instead of confetti, and added momentum so falling fast is the point.
  • A score cap in a game scored by depth. Deeper biomes used to be unlocks you bought with cores. That meant you could hit a wall mid-run, and the daily run turned out to be capped at 2,250 m for everybody, because its fixed loadout had no biomes unlocked. I removed the gate entirely.
  • Balancing with two bots. One bot doesn't dodge (it should die in about two minutes) and the other plays with its eyes open. The gap between the two is the number that tells you whether playing well matters. It went from +21% to +56%.
  • The music stuttered in debug builds. Unoptimized Swift was 24 times slower, and generating 250 ms of audio took 280 ms. The fix was compiling the rendering package with optimizations on in debug too.
  • The iPad made the game harder. The scale came from the screen width, so on an iPad you saw 31 m ahead instead of 51: 39% less time to react. The headless simulator can't see that. I capped the view's aspect ratio and filled the sides with rock walls that scroll with the camera.
  • App Review rejected 1.0 twice, both times over App Tracking Transparency. The pre-prompt had a "Not now" button, and the reviewer couldn't find the ATT dialog because it was waiting on two network calls to Google first. Now ATT goes first and doesn't wait for anything. The in-app purchase also hadn't been attached to the version. The third submission went through.

What I learned

  • "Boring" was a number before it was a redesign. Measuring first saved me from rebuilding the wrong thing.
  • A bot is a great playtester for balance and a useless one for feel. Momentum didn't move the bot's numbers at all, because the bot never hides behind a rock. Only people could tell me whether it worked.
  • Deciding what not to do with monetization (no interstitials, nothing that buys power) made every later decision easier.
  • Tools that generate assets pay for themselves the second time you need them. I've re-rendered the screenshots and icons more times than I'd like to admit.

What's next

  • Local notifications for the daily run, so a streak dies because you chose to skip it and not because you forgot.
  • A boss theme, and retuning the sound effects to the music's key. They're pure tones right now and some of them clash.
  • Tuning the Void with real player data instead of bots.

Why these categories

  • Best Game: a complete roguelike with a real skill ceiling, a daily seed and monetization that doesn't touch power, so it fits the genre instead of fighting it.
  • RevenueCat Design Award: no image or audio files in the source. Sprites, music, icons, screenshots and the App Store preview all come out of code, and the music follows how deep you are.
  • Catvertising: one opt-in rewarded placement, capped, never in the middle of a run. It's tracked with RevenueCat Ads next to the Supporter purchase, and buying Supporter gives you the reward without the ad.

Built With

Share this project:

Updates

Submission history