EngineeringHow it's built
A player action starts on the Flutter client, which already holds a Firebase Auth session. The client posts to a Cloud Function in asia-south1 with the ID token, pool id, and the intended action — register, join, start hand, bet, or move. The function verifies the token, loads the user and the pool, confirms the pool is still live, and runs the relevant domain service. The blackjack service mutates a UserGamepoolHistory document: the shuffled shoe, dealer and player hands, split hand, play options, token bank, and settlement. That document is written under users/{uid}/subscribedPools/{poolId}. The table already listens to that path, so the next snapshot drives card animation, chip totals, and enabled controls. When a hand finishes, a transactional leaderboard update reorders the pool document by remaining tokens. Pool metadata — fees, grants, windows, subscriber counts — lives in pools/{poolId} and is what the lobby queries. The client never writes those collections directly. The Flutter app is modular by scene: login, game mode, pool lobby, pool detail, table, profile, and settings, with shared design components and a single GoRouter graph.
API-drivenserver-authoritative game enginereal-time document syncmodular scene architecturecross-platformsecure token-gated mutations
Decision
Keep the entire blackjack state machine on the server.
Any client-side deal can be patched. Once a pool has an entry fee and a public rank, the shoe has to be generated and consumed where the player cannot see or replace it.
Players and operators can trust results, and the same engine can later support more variants without teaching a new client to cheat less.
Decision
Use Firestore both as the database and as the table's live channel.
A hand is a document. Listening to that document is a simpler real-time model than a custom socket protocol for this product shape, and it keeps replayable history next to the live state.
The felt stays in sync without a second transport, and every finished hand is already stored for stats and dispute review.
Decision
Isolate contest tokens from the cash wallet.
Entry is a wallet event. Play inside the pool is a token event. Mixing them would make refunds, ranking, and responsible limits harder to reason about.
A player can have many concurrent contests with clean banks, and operators can change grant sizes without rewriting wallet math.
Stack
Jack Black is a Flutter client in front of a Firebase backend hosted in asia-south1. The client owns presentation, routing, animation, and input. Cloud Functions own identity checks, the blackjack rules engine, wallet mutation, pool membership, and leaderboard writes. Firestore is the system of record and the live channel the table subscribes to. The split is deliberate: a game with stakes cannot let the device decide the cards, and a mobile table cannot wait on a full page reload to show the next card.
FlutterDartRiverpod, Provider, and BLoCGoRouterRive and playing_cardsaudioplayersFirebase Cloud FunctionsTypeScript and Node.jsFirebase Admin SDKCloud FirestoreFirebase (asia-south1)Firebase Hosting and Cloud StorageFirebase AuthenticationServer-side ID token verificationLeast-privilege Firestore and Storage rulesFirebase CrashlyticsGoogle Sign-In and Facebook LoginGoogle Mobile Ads and in-app purchases