Keeping Analytics and Platform Services Separate from Game Logic
Making Games with AI · Part 16

This article revisits development in June–July 2026 and early August, using records through August 3, 2026. References to “current” behavior, values, images, candidates, and validation describe that historical snapshot, not today's released game.
Previous: Why Ads Must Wait for User Consent
Analytics, leaderboards, payments, and ads can gradually fragment an otherwise clear game flow. Startup waits for analytics, a cleared stage waits for leaderboard submission, purchase code accumulates event parameters, and an external outage begins changing the player's result.
Kibble Street TD approached this with a responsibility boundary: the game owns business state, analytics observes it, and platform services synchronize or present information within explicitly defined limits.
Reversed responsibilities cause the coupling
Analytics and platform services support the game. They become dangerous prerequisites when an event-send failure blocks Home, a leaderboard error revokes a cleared stage, or a remote score is written into the local save.

Startup, purchases, rewards, saving, and battle settlement remain on the main business path. Analytics reports events that have happened. Leaderboards submit asynchronously after local progress is saved. Ads and purchases return explicit platform results, while still respecting local validation and saving.
A useful check is to disconnect each supporting service. Without analytics, the player should still complete the operation. Without leaderboards, the stage should remain cleared locally. External facts that authorize resources, such as payment verification or an earned ad reward, belong in their specific fulfillment contracts and still require local persistence.
The game must never wait for analytics
All custom events go through one safe dispatch entry, and business code does not wait for it. That entry distinguishes three failure stages: constructing parameters, sending synchronously, and sending asynchronously.

Each failure records the event name, stage, and original error, then ends that analytics attempt while the business function continues. Even an exception in the error logger must not propagate into purchases, rewards, saving, or battle flow.
Isolation still requires evidence. An external failure must leave the player's result unchanged and remain traceable in local logs. If either condition is missing, the implementation is hiding a problem rather than isolating it.
Events should describe business facts
Button clicks alone can fill a report without explaining what players completed. The event contract separates intent, start, and terminal outcome. Restart means a request until a battle actually begins. Purchase success means verification, fulfillment, saving, and platform finishing have all completed.

Event names and parameter definitions are centralized. Stage identifiers are strings. Uploaded errors contain normalized domains and codes, without messages, stacks, resource identifiers, or complete business objects. Custom code avoids duplicating Firebase's automatically collected first-open, session, ad-impression, and click events.
The difficult work is defining when success becomes true. AI can detect an event emitted before saving or fulfillment, but a human must settle its product meaning.
Test data must identify its environment
A Firebase project can receive development, internal-test, and production traffic. Without environment labels, test purchases and simulated ads can mix with real player behavior.

The native layer identifies xcode, testflight, production, or unknown from the app transaction environment and adds analytics_env to each custom event. Resolving it does not block Firebase initialization, since remote configuration also needs to work promptly. Up to 64 events are buffered in order during resolution. If no result arrives within five seconds, they are sent as unknown and a log records the timeout.
Unknown means that the origin could not be established. Production funnels filter for production, debug events are inspected in DebugView, and internal-test data is reviewed separately. Explicit identity is more reliable than assuming test traffic will remain invisible.
Leaderboard synchronization cannot own local progress
Game Center returns authentication, submission, and ranking data, but it is not the source of truth for local stage completion. After a win, the game updates and saves the highest cleared stage, then submits that value asynchronously. Failure preserves pending progress and an error without undoing the win.

Reauthentication, another win, or opening the leaderboard can retry the highest pending progress. Reads cover the global top 100 and the local player's entry. Platform scores never overwrite local saves, and the feature does not provide cloud-save synchronization. In production, signed-out, timed-out, or failed requests show explicit states instead of fabricated players.
Multiple native integrations also exposed a lower-level collision: the script runtime had one native callback entry. Independent module registrations overwrote one another. A shared receiver now dispatches analytics, leaderboards, ads, and payments by channel, and request IDs match responses to callers.
An authentication callback also exposed a native object-lifetime issue. The bridge moved to ARC. Authentication reads system authentication state only; display names and player IDs are obtained later from leaderboard entries. The fix belongs at the platform boundary, rather than repeated exception handling in every screen.
Automated checks do not prove backend receipt
Targeted checks covered analytics parameter errors and synchronous and asynchronous fault injection, confirming that startup, purchases, ads, rewards, and battle results continued. Leaderboard checks covered the single bridge entry, request matching, top-100 scope, saving before submission, and the absence of fabricated data on unsupported platforms. Permissions and basic configuration passed format checks.

Real evidence remained pending: DebugView order and parameters, test and production labels, rankings with two Game Center accounts, offline retries, the 100/101 boundary, and account switching. The full TypeScript check also had a Settings language-type error in this snapshot. Passing targeted checks did not establish a clean project-wide result.
What the human and AI each contribute
AI can enumerate event calls, check names and parameter types, inspect order, and inject failures in parameter construction or bridge dispatch. It can then verify that business steps still finish. It can also detect modules competing for the same callback or responses crossing requests.
The human defines what counts as a successful purchase, whether remote rankings may change local progress, which failures are isolated, which must block, and what evidence supports release. Reliable delivery of an incorrectly defined metric produces reliably misleading conclusions.
If you want to start
Choose one important flow and list what changes game state and what external systems merely observe or synchronize. Remove waits introduced only for analytics, then check parameter, synchronous-send, and asynchronous-send failures. For synchronization, name whether local or remote state is authoritative.
Disconnect analytics and leaderboards separately. Player operations should still complete and local progress should remain saved, while logs and screen states identify the failure. A useful boundary preserves both operation and diagnosis.
