Bloom
A cozy idle-garden iOS game: tap, plant, prestige — the garden keeps growing while you're away.
- Swift
- SwiftUI
- Metal
- AVAudioEngine
- StoreKit 2
- Swift Testing
- XcodeGen
Bloom is a cozy idle-garden game for iOS: tap to earn Petals, buy plants that auto-produce while you’re away, and eventually compost the season into a “New Season” prestige for permanent multipliers. About 50 achievements, a plant ladder, a Spring Seed upgrade tree. Monetization is StoreKit 2 IAP (Sunbeam packs) that’s deliberately accelerative only — it speeds you up, never gates content. No accounts, no analytics, no ads, saves are local-only.
Status
The app is feature-complete and parked. The full loop — tap, buy, prestige, achievements, IAP grants, offline earnings — works end to end, the engine test suite is green (hundreds of tests across Swift Testing and XCTest), and the app compiles clean. What’s left is human paperwork: Apple Developer enrollment, the App Store Connect record, the eight IAP entries, a real app icon (it currently ships a placeholder gradient with a 🌸), and a sandbox purchase pass on a physical device before I hit submit. It also queues behind the same two gates as everything else I ship solo right now: VAT-UE registration, and the fact that my Mac lives on beta-everything while App Store submissions want a release-candidate Xcode.
How it was built
The interesting part isn’t the game, it’s the discipline underneath it.
Bloom’s engine grew off an ordered task plan, executed one task at a time:
write the failing test first, make it pass, then face a single green/red
verification gate — verify.sh, which demands the engine suite green and
the app compiling clean — before anything gets committed and crossed off.
“Done” was never a feeling; it was an exit code. The rule I’d frame and hang
on a wall: never go green by cheating — no deleted, skipped, or weakened
tests; a clean red-blocked state beats a fake-green commit. The repo’s 120+
commits read like a metronome because of it.
Lessons learned
All that discipline didn’t prevent real bugs — it caught them. The lessons
file I kept during the build reads like a diary: an icon-compile step that
could never succeed on this Mac’s missing simulator runtime, a prestige
currency that needed Fibonacci-shaped sinks or players would 100% the tree
in one reset, and a genuinely nasty one — starting an empty AVAudioEngine
graph threw an Obj-C exception try? couldn’t catch, crashing every launch
until I learned to gate on “ready” instead of “started.” The design pass
came only after the engine was trustworthy — custom Metal shaders, a “Night
Garden” seed tree, procedural paper-cut flora. Lesson for me: process keeps
the engine honest, but taste is a separate job, and it refuses to run on a
schedule.