Sky City

A cozy 3D merge puzzle built with React Native and three.js, live on Google Play.

How it connects

React Native gamelocal Kotlin Expo modulePlay Games Services

CategoryMobile Games
Timeline2026
ContextProduct build · Solo
StatusLive on Google Play
Sky City island, Desert Sands world
70k+lines of TypeScript
~1,000Jest tests
14themed 3D worlds
10building tiers · one draw call each

The product

Merge three buildings into the next tier and grow a floating island from Campsite to World Capital, rendered as a living diorama with day cycles, fog and per-world ambient systems. No timer, no lives, no energy: nothing moves until you place a building.

Under the cozy surface: over 70,000 lines of TypeScript, about 1,000 Jest tests, a game engine in plain TypeScript with no React or three.js imports, Play purchases verified by Firebase Cloud Functions, and Play Games leaderboards through a small local Kotlin module for Expo.

Under the hood

THE ENGINEERING

  • 01

    Engine and render split: game logic is plain TypeScript with no React, three.js or Expo imports, tested with Jest on Node and driven by a seeded random generator for fair daily challenges

  • 02

    A strict performance budget: each building is one merged geometry and one draw call, repeated effects are instanced, and nothing is allocated per frame

  • 03

    Purchases verified server-side by Firebase Cloud Functions; all-time and daily leaderboards through a local Kotlin Expo module for Play Games

  • 04

    A seeded daily challenge with a streak, 57 missions and 82 challenges, a coin economy and city ranks

  • 05

    Local reminder notifications on a decaying cadence, at an hour learned from the player's own sessions, never in quiet hours

  • 06

    Every sound procedurally synthesized: 61 WAVs from a 57 KB script, zero audio licences

  • 07

    Built with an AI-first workflow: past 70,000 lines I used AI coding agents to trace and fix state bugs across the codebase; GitHub Actions builds an installable APK

Next case study

Spindle →