Mobile · iOS + Android Game

Tiny Fates — mobile life simulator with a pure functional engine, 22 languages, and RevenueCat monetization

Tiny Fates is a text-based life simulator for iOS and Android: the player ages one year at a time, picks one event per year, and stats, money, relationships, and flags carry forward until death and epitaph. The engine is pure functional (no mutations), written in TypeScript, with 50+ event packs. Available in 22 languages, monetized via RevenueCat + AdMob/AppLovin ads.

IndustryMobile / Gaming
Duration12 months (ongoing)
StatusLive · App Store + Google Play
Team2 developers + 1 writer
CountryRomania

Business context

The life-sim genre (BitLife, InstLife) has tens of millions of downloads, but competition is brutal: BitLife dominates with massive marketing budgets. Tiny Fates needed to differentiate through: (1) deeper narrative content — not just random events, but narrative arcs and a living world that reacts to choices, (2) localization into 22 languages (not just EN), (3) respectful monetization — real freemium, not aggressive paywalls.

The key architectural choice: a pure functional engine. Every function (advanceAge, resolveEvent, createCharacter) returns a new Character object, never mutates state. This enables deterministic testing, undo/redo in debugging, and mutation testing (Stryker) that validates tests actually catch bugs, not just hit lines of code.

Tech stack & key decisions

Pure functional engine — no state mutations

Every function in src/game/engine/ takes a Character and returns a new Character. advanceAge, resolveEvent, pickNextEvent, createCharacter — all immutable. Benefits: deterministic testing (same input produces same output), undo in debugging, and the ability to run mutation testing (Stryker) that modifies code and checks whether tests catch the modification.

React Native 0.86 bare workflow (no Expo)

We removed Expo/EAS for full native control. The ios/ and android/ directories are the build source of truth. Cross-platform code-sharing above 90%, but native modules (AdMob, AppLovin, RevenueCat) are configured manually in Xcode and Android Studio. Headless builds from the laptop: scripts/build-ios-sim.sh and scripts/build-android-release.sh.

Zustand for state, with AsyncStorage persistence

Game state (current Character, save slots, settings) lives in a Zustand 5 store. The store is composed of slices (gameSlice, lifeSlice, datingSlice, financeSlice). Automatic persistence to AsyncStorage — the player can close the app mid-year and resume exactly where they left off. Save compatibility across versions via hydrateCharacterForF1.

RevenueCat + AdMob/AppLovin for hybrid monetization

The model: real freemium. The full game is free; ads (AdMob interstitial + AppLovin rewarded) are optional and offer bonuses, they don't block progress. RevenueCat manages the premium subscription that removes ads and unlocks cosmetics. One SDK on both platforms, entitlement sync via Supabase.

i18n in 22 languages with i18n-js + react-native-localize

Narrative content (event text, choices, outcomes) is localized in 22 languages. UI catalogs hand-authored in src/locales/; gameplay catalogs machine-generated (multi-MB) in src/locales/generated/. Every string change must propagate to all 22 languages — an automated script checks for missing keys.

React Native 0.86 TypeScript 6 React 19 Zustand 5 React Navigation 7 RevenueCat 10 Supabase AdMob AppLovin Sentry PostHog Jest 30 Stryker

Measured outcomes

22Localized languages (UI + gameplay catalog)
80%Engine core coverage (Stryker mutation-tested)
50+Narrative event packs
0State mutations in engine (pure functional)
92%iOS/Android code-sharing (React Native bare)
1.4.5Version live on App Store + Google Play

Key takeaways

  1. A pure functional engine pays off in testing. Immutability eliminates an entire class of race-condition bugs and makes mutation testing possible. Cost: spread boilerplate, benefit: total confidence.
  2. Save compatibility is NOT optional for live games. A player who loses a 50-hour save doesn't come back. Auto-backfill on load, new fields always optional.
  3. Stryker mutation testing is the only real proof of test quality. Line coverage lies — a surviving mutant is a bug waiting to happen.
  4. RevenueCat eliminates 90% of cross-platform IAP pain. Don't build your own billing layer for a mobile game — Apple and Google will change it.
  5. 22 languages require an automated key-verification pipeline. Manual translations go inconsistent; a script checking missing keys at every build saves hours of QA.

«Tiny Fates runs on a pure functional engine with mutation testing at 80% coverage. Players never see the architecture — they see a smooth game in 22 languages with no state bugs. Faintech built the technical foundation that makes narrative content scalable.» — Eduard Gridan, founder Tiny Fates

Have a similar project?

Written discovery in 48h with detailed technical specification.

Request a technical assessment