Inspiration

If you carried a virtual pet in your backpack in the 90s, you know the feeling: rushing home because it was hungry, the beep in the middle of class, the exaggerated grief when the screen showed a little ghost.

Tame started from that memory and one question: what would that toy be if it could actually answer you?

Not a chatbot with a pet skin. A pet that lives on its own clock, has a personality it did not choose, grows up, picks a career — and happens to talk.

What it does

You adopt a dog, a cat, a dragon or a bee, name it, and care for it in real time. Hunger, hygiene, energy and mood decay with the actual clock, even with the app closed. It goes from egg to baby to adult over days, not minutes.

Along the way it develops a personality (two traits at birth, one more that emerges from how you raised it), studies, graduates into one of six careers, and starts bringing coins home. It wanders around scenes you decorate, and it interacts with what you place — climbs into the bed, sits on the couch, smells the flowers.

And it chats. It remembers your name, remembers what you told it, and answers in character.

Nobody dies. Neglect a pet long enough and it leaves — but it leaves behind an egg that inherits its name and part of its memories. The consequence stays; the trauma does not. That was a deliberate call: kids play this.

How we built it

React Native + Expo SDK 57. Zustand + MMKV for state, Reanimated for the stage, expo-router with native tabs.

The game logic is a pure module with zero React Native imports. Every rule — decay, illness, tantrums, graduation, the heir egg — lives in one testable file. Time is never simulated with background timers: the pet's state is a function of elapsed real time, computed on wake. That single decision removed a whole category of bugs and made the game work identically whether you were gone for five minutes or two days.

All the art is generated by code. 274 sprites, 2.400 lines of drawing scripts. Not a single PNG was touched by hand — pets, scenes, props, icons, the app icon and the brand logo are all drawn programmatically on a 64×64 grid and exported at 8×. Want to change a species' palette? Edit the palette, regenerate. The sound effects are generated the same way: 8-bit WAVs synthesized by a script, no samples.

The chat runs through our own Hono server. The Anthropic key never touches the app — that was non-negotiable. The persona prompt is split into a stable block and a volatile block, in that order, so prompt caching actually hits. Every three messages a Haiku call distills the conversation into a short pet.memory string that lives with the pet.

We never store conversations. Not a design detail — a constraint we set before writing the server. What persists is the distilled memory, and the persona is instructed never to record sensitive data.

Supabase for auth and for a per-user snapshot backup, with the device as the source of truth and the cloud as backup. RevenueCat for the subscription, with the RevenueCat user id bound to the Supabase uid so the subscription follows the account, not the device — and so our server can ask "is this user premium?" with no middleman.

Challenges we ran into

The math almost killed the product. We wanted to advertise unlimited chat. Then we actually did the arithmetic: with Haiku and prompt caching, each message costs about US$0.0018. At R$9.99/month (≈US$1.57 net after store fees), break-even lands around 29–31 messages a day. "Unlimited" was not a generous promise — it was a promise we could not keep.

So we capped it and rewrote the copy: "30 chat messages a day (instead of 10)". Less exciting, and true.

The interesting part is what happened next. That false claim had already spread — into four locales of the App Store listing, into the Play listing, into the website. It survived that long because the real number only existed in a server environment variable; the app had nothing to compare its own copy against.

So we made drift impossible instead of just fixing the instance:

// The perks are now a FUNCTION of the limits, not hand-written text.
perks: ({ chatFree, chatPremium }) => [
  `${chatPremium} chat messages a day (instead of ${chatFree})`,
  // ...
]

...plus a test that renders every dictionary with fake numbers (if a locale hard-codes "30", it fails), and sweeps the whole dictionary for promises we have made before and could not keep — unlimited, coin allowance.

The reviewer account was set up backwards. We had given the store reviewer a courtesy premium flag so they could "see everything". That is exactly wrong: what a reviewer needs to validate is the purchase. In review, store purchases run in sandbox and cost nothing — so the account should arrive without a subscription and buy one. A pre-unlocked account would have hidden a broken purchase flow until it reached a paying customer.

A build that would have shipped broken. .env was in .gitignore, and EAS builds from what git can see. The production build would have gone out with no Supabase URL, no app token and no RevenueCat keys — and since the whole app sits behind login, it would have been dead on arrival. Caught it before spending the build.

Accomplishments we're proud of

  • The pet feels alive without draining your battery. No background timers, no push tricks. Just honest arithmetic on elapsed time.
  • Art that is code. Regenerating the entire visual identity is one command. New species, new palette, new icon — no design round trip.
  • Three languages from day one (pt/en/es), with typed dictionaries where a missing key fails compilation rather than shipping an empty string.
  • A monetization model we can defend out loud. No ads, ever. The whole game is playable free. The subscription sells more of what you already like, not access to what you already had.
  • 249 tests (218 in the app, 31 on the server) guarding the rules that matter: the economy stays winnable for free players, the copy cannot outrun the code, and the referral flow cannot be farmed.

What we learned

Prompt caching is a product decision, not an optimization. Splitting the persona into stable and volatile blocks is what makes a conversational pet economically possible at R$9.99. Get the order wrong and the business model goes with it.

Pricing is a feature. We did not discover the chat limit in a spreadsheet at the end — we discovered it while writing the store listing, and it changed the product. Doing that math early would have saved a day of correcting copy across two stores, three languages and a website.

Write the guard, not the fix. Every bug we fixed by editing one string came back somewhere else. The ones we fixed by making the wrong state unrepresentable did not.

What's next for Tame a Pet

  • A social square where pets visit each other — the reason the coin balance will eventually have to move server-side.
  • An accessory workshop: craft items instead of only buying them.
  • More careers, and a diploma you can hang on your wall as a scene item.
  • Photo album: your own photo in the pet's picture frame — held back on purpose until moderation and COPPA/LGPD are properly handled.

Built With

Share this project:

Updates