Case study
MoteCraft: 1,000+ touch-reactive worlds, native on iOS and Android
MoteCraft is a catalogue of living visual worlds: mandalas that bloom, light you draw, nebulas you stir. Each is hand-built with GPU shaders in native code on both platforms. Product, design, two native apps, catalogue backend, admin panel and store releases by one engineer.



- Role
- Product, design, engineering, release, operations
- Platform
- iOS (SwiftUI + Metal) and Android (Kotlin + Compose + OpenGL ES)
- Stack
- Swift · Metal · Kotlin · Jetpack Compose · OpenGL ES · Firestore · Cloud Functions · Next.js
- Period
- 2026, live on both stores
Context
People wanted a calm minute: put a finger on the screen and watch something answer. Existing apps were either a handful of effects or a game engine wrapped around stock particles.
The product bet was volume and craft together: hundreds of distinct worlds, each reacting instantly to multi-touch, with no ads and a Pro tier for the full catalogue.
My role
- Defined the product, the free and Pro split, and the release cadence.
- Built the iOS app in SwiftUI with Metal shaders and the Android app in Kotlin with Jetpack Compose and OpenGL ES. Kotlin was outside my core stack; AI coding agents got me productive in days.
- Built the catalogue backend on Firestore and Cloud Functions, the admin panel, the Next.js marketing site, and the store listings.
Constraints
- Two native codebases maintained by one person, with the same worlds behaving the same on both.
- Shaders that feel instant on mid-range phones and still look right on the newest ones.
- Already-shipped binaries must keep working after every backend change; the catalogue is data, not code.
Key decisions
Decision 1
Native on each platform instead of one cross-platform codebase
Touch latency and GPU access decide whether a world feels alive, so each app talks to Metal or OpenGL ES directly. The cost is two codebases and a second language to learn; the gain is frame-rate and touch response that a bridge would not give.
Decision 2
The catalogue lives in Firestore with an add-only schema and version bands
Worlds, tiers and availability are data the apps read, gated per platform and app version. New fields are only ever added, never changed, so an old binary never breaks. It constrains how the schema can evolve, and it means a world can ship or be pulled without a store release.
Decision 3
An offline renderer for shader previews
A small Swift command-line tool renders each Metal world to an image without a device, which produces store previews and catches a broken shader before QA. Extra tooling to maintain, far less manual capture.
What I built
- iOS app: SwiftUI shell, Metal fragment shaders per world, multi-touch input, Moments recording up to 45 seconds at 1080p.
- Android app: Kotlin, Jetpack Compose, OpenGL ES ports of the same worlds.
- Catalogue backend on Firestore and Cloud Functions with per-platform and per-version gating; admin panel for curating worlds and tiers.
- Pro subscription (monthly, yearly, lifetime) through store billing; Firebase Analytics with custom dimensions for the user journey.
- Release checklist per store version: Firestore verify gate, builds, beta, submit, post-release flag flips.
- AI coding agents scaffolded ports, tests and refactors under skill files and hooks I wrote. I reviewed every change before it merged.
Validation
- Every world passes a launch QA pass on real devices before it is pulled into the production catalogue.
- The offline renderer produces an image per world so a broken shader is visible before a device test.
- A Firestore verify gate runs before each store submission; analytics custom dimensions confirm the user journey after release.
Release and operation
- Both stores, with a per-version release checklist archived after each ship.
- Worlds and tiers change through the admin panel without an app release; backward compatibility is non-negotiable.
- Operated by one person: catalogue curation, store listings, analytics, and support.
What changed, and what I'd do next
- Live on the App Store and Google Play as two native apps.observed
- 1,000+ worlds in the live catalogue, 200+ free.measured
- Around 150 monthly active users.measured
- No backend change has broken a shipped binary.design goal
Measured: a number from logs, analytics or a release pipeline. Observed: seen in production without a formal measurement. Design goal: what the system is built to guarantee.
What I'd do next
- Store preview video and rating prompts at the right moment, both already specified from a competitor audit.
- Automate the cross-platform parity check so an iOS world and its Android port are compared from the renderer output.