Realpolitikin: Project Story
Inspiration
I've always loved the 1959 tabletop classic Diplomacy, but mobile versions of it fill an empty seat with a scripted heuristic that never really says anything and never really means anything.
Adding a real LLM bot is a weekend project. Doing it for real, in a shipping app, is harder: every bot turn becomes a metered API call with an actual dollar cost, unlike a heuristic that's free to run a million times. That's the real design problem, and RevenueCat is what solves it. Entitlement state is mirrored server-side via a RevenueCat webhook, and the Cloud Function that's about to call Groq checks Firestore, never the client. The subscription isn't bolted on after the fact, it's the mechanism that makes offering real AI opponents in an existing, beloved game genre financially sane.
The spark to actually build this came when a competing Diplomacy-style app landed on the subreddit and got real traction without solving either problem. That's when this became a sprint: AI opponents that behave like actual negotiators, monetized in a way that doesn't bankrupt itself on day one.
What it does
Realpolitikin is a seven-power negotiation strategy game for iPhone, iPad, Mac, and Android. Play solo against bots with zero setup, or host a full seven-player online game with real-time private and broadcast diplomatic cables.
The thing I'd point a Shipaton judge at first:
- AI opponents monetized like the metered resource they actually are. Every LLM bot turn is a real, billable call to Groq that reads the recent cables and negotiates in character, so it sits behind a RevenueCat entitlement (
realpolitikin Pro) checked server-side by the Cloud Function before it spends the tokens, backed by a webhook (functions/src/revenuecatWebhook.ts) that mirrorsusers/{uid}.isProinto Firestore. Free players still get a complete seven-power game with heuristic bots filling every open seat, they just don't get an opponent with a personality. - LLM bots with actual personalities. Each bot (balanced, aggressive, defensive, opportunist, or wildcard) negotiates in character, promising support one turn and breaking it the next. Heuristic bots remain the free-tier fallback and the only option during the build phase, where an LLM call doesn't add enough negotiating value to justify its cost.
- Adjudication that can't be gamed. Every multiplayer turn resolves inside a Cloud Function, never on a player's device, closing the gap where the last player to submit orders previously computed and pushed the resolved state. Moving resolution server-side behind a transactional lock means no one can see everyone's hand before locking in their own.
A power wins outright at 18 of the 34 supply centres on the board, just past a strict majority,
$$\left\lceil \frac{34}{2} \right\rceil + 1 = 18$$
so the win condition is deliberately harder than "more than everyone else."
How we built it
- Client: Expo and React Native with the New Architecture, Expo Router, TypeScript throughout.
- Map rendering:
@shopify/react-native-skiafor the board itself, with territory labels and supply-centre dots extracted from the raw SVG via regex at load time (Skia'sImageSVGsilently drops<text>/<tspan>elements, so those get re-rendered as React Native overlays instead). Pan/zoom viareact-native-zoom-toolkit. - Backend: Firebase, with Firestore for all shared game state, Auth (Google, Apple, email/password), and Cloud Functions on Node for adjudication, bot dispatch, and scheduled deadline sweeps. Every shared-state write goes through a Firestore transaction; an
adjudicating: trueflag on the game document acts as a distributed lock so double-adjudication can't happen even when our own writes re-trigger the function. - LLM bots: Groq, with per-personality temperature tuning and a hard fallback to the heuristic bot on any error, so a bot's turn is never allowed to stall the whole table waiting on an API call.
- Monetization, the part I think is the real showcase here: RevenueCat isn't a paywall dropped in front of a finished app, it's the trust boundary the whole AI-cost model hangs off of: a 5-day trial converts to an annual
realpolitikin Proplan gating multiple concurrent online games and LLM bot opponents, with the customer linked to the same Firebase Auth uid vialib/purchases.ts. On the server,functions/src/revenuecatWebhook.tsre-fetches the customer's current state from RevenueCat's V2 API on every event and mirrors it into Firestore, and that mirror (not the client SDK) is whatonOrdersChangedchecks before spending a single Groq token. - Cross-platform reach: the same iOS build runs natively on Apple Silicon Macs via "Designed for iPad" support, no separate Mac build or Catalyst wrapper needed, plus a full Android build with matching adaptive icons and Google/Apple sign-in.
Challenges we ran into
- Deciding where the free/paid line actually goes. This wasn't "put AI behind a paywall" as one flag, it meant pricing each phase's token cost separately. The build phase stayed heuristic-only even for Pro accounts, because an LLM call there doesn't add enough negotiating value to justify the cost.
- RevenueCat's V1 vs V2 API keys are not interchangeable. V2 secret keys are project-scoped, so the project id has to live in every request URL; a V2 key against the V1 subscriber endpoint fails with an opaque, unactionable error. Cost about an hour of confused debugging before landing on the current V2-only webhook.
- A security tradeoff hiding as a bug report. "The bots stopped replying" turned out to be the correct behavior of the server-side entitlement check, silently blocking LLM dispatch for an account whose Pro status hadn't been backfilled yet by the webhook. A security fix and a regression can look identical from the outside.
- The trust-and-signing rabbit hole. Google Sign-In needed three different certificates registered with Firebase depending on build path (local debug, EAS upload, Play's own re-signing key), and
DEVELOPER_ERRORgives you none of that context.
Accomplishments that we're proud of
- A monetization model that matches the actual cost shape of the feature it protects: static content stays free, per-call AI inference sits behind the paywall. Not an arbitrary feature gate bolted on after the fact, but RevenueCat entitlement state read at the exact moment the app is about to become a Groq bill.
- Entitlements that are actually enforced server-side, never just checked client-side and trusted, which matters enormously once a paid feature is "the app can spend my Groq budget," not just "unlock a cosmetic."
- Multiplayer adjudication that's actually fair: no player, including the host, can influence or preview a turn's outcome before it locks for everyone simultaneously.
- Bots that read the room. Watching a wildcard-personality bot offer an alliance in one cable and issue the order that breaks it in the same turn is, unreasonably, one of my favorite things this project does.
- A real, working cross-platform footprint (iPhone, iPad, Mac, and Android) built and shipped solo.
What we learned
- Client-side trust is a real liability the moment a gated feature costs money per invocation rather than being a one-time unlock; moving the Pro check into Firestore (written only by a webhook, never by the client) closed a real hole, not a theoretical one.
- RevenueCat is infrastructure for AI cost control as much as it's a paywall SDK. Once a feature's cost is "a few cents every time a user triggers it" instead of "free to serve once built," the entitlement check stops being a UX gate and becomes the thing standing between the app and an unbounded API bill.
- "Looks correct" and "verified correct" are different things: the Android tab bar icons were silently invisible for a long stretch because a Material Icons fallback map had four stub entries left over from a template and none of them matched anything the app actually used. Nothing crashed; it just quietly rendered nothing.
- Platform compliance rules (Sign in with Apple ordering, mandatory account deletion, EULA links) are worth reading before submission, not after; each one is a full review-cycle round-trip to fix.
What's next for Realpolitikin
- Real usage-based metering instead of a flat Pro/free line. A binary subscription is a blunt instrument for a cost that's actually per-call, so RevenueCat's consumable and metered-billing primitives could let a free player buy a small bundle of LLM bot turns instead of committing to a full annual plan. The entitlement plumbing already in place (server-side mirror, webhook, per-call check before dispatch) is most of what it'd take to get there.
- A Windows build. Skia already ships a CanvasKit/web renderer, so the plan is a web build wrapped in Tauri rather than a native rewrite, with RevenueCat's Stripe-backed Web Billing for purchases and a proper system-browser OAuth flow (not an embedded webview, which Google blocks) for sign-in.
- Tightening Firestore security rules for production now that server-side adjudication has been proven live in practice, not just in design.
- Deeper host tooling and reliability tracking, so the pool of players you get matched with stays honest over time.
- More bot personality tuning. The negotiation quality of the LLM bots is the single biggest lever on how the game actually feels to play, and the more that quality improves, the stronger the case for metering it finely rather than gating it as one flat feature.
Built With
- claude-code
- firebase
- firestore
- groq
- react-native
- revenuecat
- skia
Log in or sign up for Devpost to join the conversation.