Work / Case study 06

Terrarium

Real-world exploration as a living world

Role
Solo — product, design, engineering
Timeline
June — July 2026 (~1 month)
Stack
Swift 6, SwiftUI, RealityKit, SwiftData, Mapbox v11, H3
Release
In development

It started with moving to San Francisco

The idea came up when I moved to SF in May 2026. Exploring a new city brings me real moments of joy — but having never lived on the West Coast, deciding where to go was surprisingly hard. Every list app answers "what's nearby" with a wall of pins and reviews, which is information, not motivation. What I wanted was something closer to a game: pick a destination for me, make getting there feel like uncovering territory, and give me a small reward for actually showing up.

Terrarium is that, built as a native iOS app around one core loop: recommend → arrive → grow. It suggests a nearby place worth visiting, tracks the walk with a fog-of-war map, and awards a collectible specimen when it verifies you really arrived — so the city gradually becomes a map you've personally cleared.

Three surfaces

  • Home — an RPG-style atlas with fog-of-war, a weather-driven sky state, and level progress that reflects how much of the world you've uncovered.
  • Drift — a location-tracked walking "ramble" that lays down a breadcrumb trail and reveals hex tiles of the map as you move.
  • Anchor — a concierge that ranks one nearby place to visit, lets you re-roll, and verifies arrival to award the specimen.

The engineering

The core technical problem is that the app's central event — "you actually went somewhere" — comes from the physical world, which is noisy, asynchronous, and impossible to script in a test. Arrival detection runs as an explicit expedition state machine driven by live location, with a manual check-in fallback for when GPS is being GPS. Modeling it as a state machine, rather than scattered location callbacks, is what makes it testable: every transition can be exercised with simulated coordinates.

The same discipline shapes the whole architecture. A pure Domain layer (Foundation and simd only) holds the app's logic, and every external integration — weather, location, maps, live venue hours — sits behind a Providing/Managing protocol backed by a stub. The result is that the app compiles and runs fully offline, with every preview and test running against deterministic fakes. It's the same principle you'd apply to any system that has to be evaluated against a nondeterministic outside world: isolate the nondeterminism behind a seam, then test the logic on your side of it.

The visible layer is a fog-of-war rendered over a Mapbox v11 map, indexed with H3 hexagonal cells so "revealed territory" is a well-defined grid rather than a blurry radius. The codebase is strict-concurrency Swift 6 throughout — MainActor default isolation, @Observable view models — so the compiler, not convention, enforces that location streams and UI state can't race.

The content is hand-curated rather than scraped: a catalog of SF and Bay Area places with verified hours, each with an authored field note — because a recommendation engine is only as good as what it's allowed to recommend.

Where it stands

Built solo in about a month: ~29,000 lines of Swift across 131 files and 135 commits. The core loop — recommendation, verified arrival, fog-of-war reveal, specimen collection — works end to end, and the app is on its way to the App Store. This page will get the link when it lands.