SDK Touch Surface
A common pre-integration question is “how invasive is this SDK in my codebase?” The honest answer is one touch-site per SDK concern you decide to use, and concerns are orthogonal — you only touch what you turn on. This page shows what that looks like in practice, using the dogfood game Numbers-Go-Up as a reference.
We’re explicit about this because pre-sales pitches that imply “single-file integration” are misleading. The SDK is a singleton autoload, so you can technically call it from one file — but if you only emit track() from one file, you’re not exercising the SDK, you’re paying for it. Real integration touches a handful of files, and that’s by design: each concern lives next to the domain code that triggers it.
The Six Concerns
Section titled “The Six Concerns”Quest Data is organized as orthogonal concerns. Pick the ones you need; ignore the rest. Each concern has a small Public-API surface and a natural touch-site in your codebase:
| Concern | Public API | Where it naturally lives |
|---|---|---|
| Event Tracking | track(), track_ui(), track_error(), track_purchase(), set_user_properties() | Inside the gameplay code where the event happens — UI buttons, win/lose conditions, screen transitions. Disperse, not centralized. |
| Balancing / Game Data | bind_balancing(), bind_balancing_pivot(), get_game_data(), fetch_tables() | Wherever you load and parse game-data resources (.tres). Usually 1–2 files for tabular content. |
| Remote Config | fetch_remote_config(), get_config(), config_freshness_seconds(), is_config_stale() | A settings_manager.gd or your boot sequence — wherever feature flags live. 1 touch-site. |
| Achievements & Progression | start_progression(), complete_progression(), unlock_achievement(), submit_score() | A dedicated achievement_tracker.gd / leaderboard_tracker.gd that listens to your existing domain signals. 1–2 files even when the triggers come from a dozen places — see Pro-Pattern below. |
| Cloud Saves | save_game(), load_game(), SAVE_CONFLICT_OVERWRITE etc. | Your existing save-system file. 1 touch-site — the SDK is a transport layer, not your save state. |
| Logging | log_debug() / log_info() / log_warning() / log_error() (+ tagged *_t variants) | Anywhere — these replace print() / push_warning() / push_error(). Many touch-sites by design, usually behind a thin tagged-logger wrapper. |
Five of these concerns are touch-once-and-forget (Cloud Saves, Remote Config, Achievements, Leaderboards, Balancing). Two are disperse-by-design (Event Tracking, Logging) — they instrument the domain code itself, so the surface area scales with how much you instrument.
Reference: How Numbers-Go-Up Uses the SDK
Section titled “Reference: How Numbers-Go-Up Uses the SDK”Numbers-Go-Up is the dogfood game we build the SDK against. It uses five of six concerns (no Remote Config currently) and has ~11 files with direct QuestData.* calls. Here’s the full inventory:
| File | Concern | What it does |
|---|---|---|
scripts/views/idle_app.gd | Event Tracking, User Properties, Logging | Top-level screen transitions: track_ui(), track("screen_view", ...), set_user_properties() |
scripts/systems/scavenging/scavenging_manager.gd | Event Tracking, Cloud Save Bridge | Domain-specific track() events on scavenge outcomes |
scripts/components/changelog_notification.gd | Event Tracking | track() when a player opens/dismisses an in-game changelog banner |
scripts/data/building_data.gd | Balancing | One marker comment for bind_balancing() — the actual call sits in idle_manager |
scripts/systems/idle_game/idle_manager.gd | Balancing (Pivot), Cloud Save Bridge | bind_balancing_pivot() for building_costs, building_production. Bridges saves. |
scripts/systems/idle_game/save_manager.gd | Cloud Saves | save_game() / load_game() round-trip with conflict resolution |
scripts/systems/analytics/achievement_tracker.gd | Achievements | Listens to MilestoneManager + ScavengingManager signals, calls unlock_achievement() |
scripts/systems/analytics/leaderboard_tracker.gd | Leaderboards | 5 game-side hooks calling submit_score() for W8 leaderboards |
scripts/systems/changelog/changelog_manager.gd | Changelogs (read-only) | Polls QuestData for new patch notes |
scripts/components/quest_data_debug_panel.gd | Dev tooling | Reads dump_state() for the in-game debug overlay |
scripts/utils/idle_logger.gd | Logging (wrapper) | Thin tagged-logger wrapper. Customer code calls IdleLogger.info(...), which forwards to QuestData.log_info_t("idle", ...) |
Files beyond these 11 (~10 more) only call the IdleLogger wrapper — they never see QuestData directly. That’s the Logging concern doing its job: the customer codebase decouples from the SDK behind one thin file.
Anti-Pattern: The Central GameAnalytics.gd Wrapper
Section titled “Anti-Pattern: The Central GameAnalytics.gd Wrapper”A tempting first instinct is to put all SDK calls behind a project-level wrapper:
# DON'T DO THISclass_name GameAnalyticsextends RefCounted
static func track_level_complete(level: int, score: int) -> void: QuestData.track("level_complete", {"level": level, "score": score})
static func track_purchase(item: String, price: int) -> void: QuestData.track("purchase", {"item": item, "price": price})
# … 80 more wrappers …Three reasons this is the wrong move:
- You re-invent the SDK badly. Quest Data already provides typed entrypoints (
track_purchase,track_ui,track_error) for the common cases. Re-wrapping them adds indirection without value. - Your wrapper becomes a god-object. Every new event is a new wrapper function. Reviewers see “GameAnalytics.gd +1 function” on every PR and stop reading. Bugs hide in the wrapper layer.
- It hides the call-site context. When
track()lives next to the code that triggers the event, the call documents why it’s tracked. When it sits inGameAnalytics.gd, you have to grep to find the trigger.
The exception is Logging — there a thin tagged-logger wrapper is the right pattern (see IdleLogger above). Logging is unique because (a) every project wants a project-specific tag, and (b) the wrapper provides a clean fallback when the SDK isn’t loaded (tests, early autoload init). Don’t generalize from logging to the other concerns.
Pro-Pattern: Concern-Trackers When Triggers Are Disperse
Section titled “Pro-Pattern: Concern-Trackers When Triggers Are Disperse”When the triggers for a concern come from many domain modules, a single concern-specific tracker file is the right answer — not per-domain-module direct calls, and not a global wrapper.
Numbers-Go-Up’s analytics/achievement_tracker.gd is the canonical example. It listens to signals from MilestoneManager, ScavengingManager, and others, and calls QuestData.unlock_achievement() from one place:
# achievement_tracker.gd (paraphrased)class_name AchievementTrackerextends Node
func _ready() -> void: MilestoneManager.milestone_reached.connect(_on_milestone) ScavengingManager.rare_drop.connect(_on_rare_drop)
func _on_milestone(id: String) -> void: QuestData.unlock_achievement("milestone_" + id)
func _on_rare_drop(item: String) -> void: if item == "ancient_amulet": QuestData.unlock_achievement("first_amulet")Why this beats both alternatives:
- Direct calls from
MilestoneManagerwould pollute domain code with analytics knowledge. - A central
GameAnalytics.gdwould mix concerns: tracker for achievements, tracker for leaderboards, tracker for purchases, all in one file.
The rule: one tracker per concern, signal-driven, lives in scripts/systems/analytics/. Domain modules stay clean.
What This Means for You
Section titled “What This Means for You”If you’re about to integrate Quest Data, expect:
- Setup: 3 steps from Godot SDK Setup, zero boilerplate in your code.
- Per concern you use: 1 natural touch-site (Balancing, Cloud Saves, Remote Config, Achievements, Leaderboards). Total 3–10 LOC per concern.
- Event Tracking: as many touch-sites as you want to instrument. Don’t try to centralize — call
track()next to the gameplay event. - Logging: optionally one wrapper file (
*_logger.gd) to provide your project’s tag and a no-SDK fallback for tests.
For a dogfooded reference, look at the 11-file inventory above and read the actual files in the linked NgU repo. That’s the real shape — not a marketing pitch.
See Also
Section titled “See Also”- Godot SDK Setup — install + configure
- Wait for SDK Ready — the one mandatory pattern at boot
- SDK Migrations — version-by-version migration guides
- Tutorial Games — six finished games with embedded SDK calls