Analytics¶
Snowpulse exposes analytics as an always-present service through
AppContext::analytics(). Application code uses one portable API while
AppConfig selects AnalyticsBackend::None, AnalyticsBackend::ByteBrew, or
AnalyticsBackend::GameAnalytics once during application setup.
None is the safe default and means that no provider receives data. The API
still compiles, the service pointer is never null, and every operation safely
returns false without logging an error, throwing, or crashing.
Configure a Backend¶
Configure analytics before constructing App. Credentials remain
application-owned configuration and must not be committed to the engine:
snowpulse::AppConfig makeAppConfig() {
snowpulse::AppConfig config;
config.bundleId = "com.example.application";
config.analytics.backend = snowpulse::AnalyticsBackend::GameAnalytics;
config.analytics.buildVersion = "1.4.0";
config.analytics.debugLogging = false;
config.analytics.maxPendingEvents = 256;
config.analytics.gameAnalytics.gameKey = loadGameAnalyticsKey();
config.analytics.gameAnalytics.secretKey = loadGameAnalyticsSecret();
// GameAnalytics requires these allowlists before initialization.
config.analytics.gameAnalytics.resourceCurrencies = { "CREDITS" };
config.analytics.gameAnalytics.resourceItemTypes = { "content" };
return config;
}
class Application final : public snowpulse::App {
public:
Application() : App(makeAppConfig()) {}
};
For ByteBrew, select AnalyticsBackend::ByteBrew and set
analytics.byteBrew.gameId and analytics.byteBrew.sdkKey. A full web build
also requires a non-empty buildVersion. Native platforms use package metadata
when the build version is empty and the provider supports that fallback.
autoStart defaults to true. The engine starts the selected real provider in
App::init(), advances asynchronous readiness from App::update() even while
portal-paused, and flushes/shuts it down from App::shutdown(). Backend
switching after setup is deliberately unsupported because it can duplicate
provider sessions.
Consent-Delayed Startup¶
Applications that require consent before tracking should disable automatic startup:
config.analytics.autoStart = false;
// Later, after the application's consent flow succeeds:
appContext()->analytics()->start();
Calls made before start() return false and are dropped. While an
asynchronous provider is Starting, calls enter a bounded FIFO. When it becomes
Ready, the engine drains them in order. If the FIFO reaches
maxPendingEvents, the newest call is rejected. The facade does not add offline
persistence; network behavior after dispatch belongs to the selected SDK.
stop() is terminal for the current Analytics instance. It clears queued
calls, ends the provider session, and makes future start() and send calls
return false.
Send Events¶
All send methods return true when the provider dispatched the call or the
facade accepted it into the startup FIFO. Callers may ignore the result.
auto* analytics = appContext()->analytics();
analytics->sendEvent("operation_started", {
{ "source", "daily" },
{ "attempt", 3 },
});
snowpulse::AnalyticsProgressionEvent progression;
progression.status = snowpulse::AnalyticsProgressionStatus::Complete;
progression.progression1 = "chapter_1";
progression.progression2 = "step_4";
progression.score = 1250;
analytics->sendProgressionEvent(progression);
snowpulse::AnalyticsResourceEvent resource;
resource.flow = snowpulse::AnalyticsResourceFlow::Sink;
resource.currency = "CREDITS";
resource.amount = 5.0;
resource.itemType = "content";
resource.itemId = "feature_access";
analytics->sendResourceEvent(resource);
snowpulse::AnalyticsPurchaseEvent purchase;
purchase.store = "application_store";
purchase.currency = "USD";
purchase.amountMinorUnits = 499;
purchase.currencyExponent = 2; // 499 with exponent 2 represents USD 4.99.
purchase.itemType = "subscription";
purchase.itemId = "premium_monthly";
purchase.cartType = "checkout";
purchase.properties = { { "introductory_offer", true } };
analytics->sendPurchaseEvent(purchase);
snowpulse::AnalyticsAdImpression impression;
impression.action = snowpulse::AnalyticsAdAction::Show;
impression.type = snowpulse::AnalyticsAdType::RewardedVideo;
impression.network = "example_network";
impression.placement = "completion";
impression.adUnitId = "rewarded_1";
impression.revenue = 0.012;
impression.revenueCurrency = "USD";
impression.properties = { { "mediation", "example_adapter" } };
analytics->sendAdImpression(impression);
snowpulse::AnalyticsErrorEvent error;
error.severity = snowpulse::AnalyticsErrorSeverity::Warning;
error.message = "A recoverable request timed out";
error.properties = {
{ "operation", "content_fetch" },
{ "will_retry", true },
};
analytics->sendErrorEvent(error);
analytics->setUserProperty("segment", "onboarding_b");
Purchase amounts are non-negative integer minor units; currencyExponent
describes how many decimal places the currency uses and must be at most 9.
receipt and signature are optional provider data. Ad revenue is optional,
but when present it must be finite and non-negative. Error messages must contain
1–1024 characters. Custom user-ID assignment is intentionally absent: ByteBrew
exposes its generated identity and cannot preserve GameAnalytics custom-ID
semantics portably.
AnalyticsValue accepts strings, signed and unsigned integers, floating-point
numbers, and booleans. Event names, property keys, progression segments,
currency names, item identifiers, and other portable identifiers must contain
1–64 ASCII letters, digits, _, or -. Invalid identifiers, non-finite
numbers, malformed payloads, missing credentials, provider exceptions, and
JavaScript/JNI failures return false at the adapter boundary.
State and Availability¶
Use state() for NotStarted, Starting, Ready, Unavailable, or
Stopped. isAvailable() means the selected SDK and required credentials are
present on this platform; isReady() means calls dispatch immediately.
An unsupported or absent provider behaves like an unavailable null service. In
particular, choosing ByteBrew on desktop or selecting any provider in a
PLAYABLE_AD web build is safe but never becomes ready.
Provider Mapping¶
| Portable call | GameAnalytics | ByteBrew |
|---|---|---|
sendEvent |
Design event | Custom event |
sendProgressionEvent |
Progression event | snowpulse_progression_<status> custom event |
sendResourceEvent |
Resource event | snowpulse_resource_<flow> custom event |
sendPurchaseEvent |
Business event | Native purchase where available; otherwise snowpulse_purchase |
sendAdImpression |
Ad event where supported; desktop falls back to snowpulse_ad_impression design event |
Native mobile ad event where compatible; otherwise snowpulse_ad_impression |
sendErrorEvent |
Error event | snowpulse_error_<severity> custom event |
setUserProperty |
Global custom event fields | Custom data attribute |
ByteBrew mobile custom-event string payloads percent-encode delimiter and
unsafe characters before producing the SDK's key=value; representation.
Platform Packaging¶
| Platform | GameAnalytics | ByteBrew |
|---|---|---|
| Android | 7.0.2 from the pinned provider repository, including required transitive dependencies and R8 rules | Uses the vendored ByteBrew.aar when supplied |
| iOS | Vendored 5.0.2 XCFramework, privacy manifest, and required Apple frameworks | Uses the vendored ByteBrew.xcframework when supplied |
| Windows/macOS | Pinned 5.3.1 C++ source built as a static target for the active OS/architecture | Unsupported; safe unavailable backend |
Web FULL |
Vendored local 5.0.0 IIFE/UMD asset | Vendored local 1.0.1 asset |
Web PLAYABLE_AD |
Excluded | Excluded |
The proprietary ByteBrew mobile packages are not publicly redistributable from the sources used by Snowpulse. Obtain project-approved copies and place them at:
snowpulse/external/analytics/android/bytebrew/ByteBrew.aarsnowpulse/external/analytics/ios/bytebrew/ByteBrew.xcframework
Record the supplied version, acquisition date, SHA-256, license, and redistribution approval in the adjacent README before distributing the engine. Without those files, ByteBrew remains safely unavailable on Android/iOS; this does not affect GameAnalytics or the portable C++ API.
Generated standalone Android projects receive repository, version-catalog, dependency, verification-checksum, and shrinker changes through the normal updater:
Full generated web builds consume the provider assets directly from the engine's CMake target, so existing application source and credentials do not need migration. Generated playables retain the complete facade but omit provider bridge sources, SDK JavaScript, and provider banners from the artifact.
For provider-specific dashboard setup, see the official ByteBrew SDK overview and GameAnalytics SDK overview.