Inspiration
A doom-scroll-named game that does the opposite of what its name implies: after ten minutes of continuous play it asks, once, whether the player wants to call it a day — a dismissible, non-forcing nudge, not a lockout, and gameplay never pauses for it.
What it does
A one-tap endless runner with a mid-air double jump, two obstacle types (boxes, spikes) and a continuous speed ramp tied to distance. Three modes: Classic (endless, one hit ends it), Daily (the same seeded obstacle course as every player, today only, via a deterministic PRNG), and Zen (no game over). A single opt-in rewarded ad ("Mola" — take a break) is the only ad format in the app: never a banner, never an interstitial, never a "continue" offer. Watching it earns a hint (capped at 3/day) and never revives a run or grants a life. One non-consumable purchase, "Ad-free + destek," removes the ad offer and grants the 3 daily hints automatically. Before a session the player can set an intention — a number of runs or minutes — and when it is reached the game says "that's it for today" and means it: an honest stop screen with no "one more run" hook, no countdown, no loss language. Run Complete shows the distance against the record as a plain difference, Daily shows yesterday next to today, and Zen is one tap away. Records and intentions stay on-device; no account. Nineteen languages in the in-app picker, 17 of them passed a two-reviewer translation check and the full set passed an independent audit.
How we built it
Expo SDK ~54.0.33, React Native 0.81.5, TypeScript, Expo Router, react-native-reanimated + react-native-svg for the render loop, frame-rate-independent physics (every step scaled by dtMs / REF_FRAME_MS, clamped so a backgrounded app can never tunnel an obstacle through the player). RevenueCat (react-native-purchases ^9.14.0): entitlement premium, offering doomscroll_default, product doomscroll_adfree (a single lifetime package, no first-package fallback if that exact package is missing — a deliberate refusal to sell the wrong SKU). Advertising: react-native-google-mobile-ads ^16.3.0, wired only to the opt-in rewarded unit; a static gate greps the codebase for BannerAd/InterstitialAd and fails the build if either appears. Sentry crash reporting is bundled but not yet active.
Challenges
Keeping the "one ad format, opt-in only" claim true under refactors needed more than a code review — it needed a negative-controlled static gate (scripts/verify-monetization-safety.mjs) that greps the actual src/+app/ tree for banner/interstitial ad components on every run, because a claim in a listing file drifts from the code the moment nobody is watching it. The session nudge had a similar trap: the play-time clock had to be held across runs and count only simulated frames, or a backgrounded app would either never trigger the nudge or trigger it too early.
Accomplishments
A monetization model with zero dark patterns by construction, not by promise — the "no banner, no interstitial" claim is a CI gate, not a paragraph; an endless runner that asks you how much you want to play and then stops when you said so; a Daily mode whose determinism (same seed, same course, for everyone) is proven by test rather than assumed; and an accessibility pass (44dp touch targets, ≥4.5:1 contrast, labelled controls) checked at the source level for every screen and every theme token.
What we learned
A privacy or monetization claim that lives only in a listing document is a claim that silently stops being true; treating "STORE_TRUTH.md" as a live, re-run artifact against the shipped code — rather than a one-time write-up — is what caught the earlier v1 "no ads" copy after the v1.1 rewarded-ad feature landed.
What's next
No iOS release yet: the AdMob iOS app ID placeholder was removed until a real one is set, and the EAS iOS submit profile is pending the same. The render architecture (currently SVG + Reanimated) has an accepted, deferred rewrite to Skia/SharedValue rendering, tracked as technical debt rather than shipped now.
Built With
- admob
- expo-router
- expo.io
- github-actions
- jest
- react-native
- react-native-google-mobile-ads
- react-native-purchases
- react-native-reanimated
- react-native-svg
- revenuecat
- sentry
- typescript
Log in or sign up for Devpost to join the conversation.