SDK / Godot

Player reports

ExperimentalGodot 4.7+

Players tell you about a bug, a slow spot or an idea from inside your game, and each report carries what the SDK gathered on its own: a screenshot, the last minute of frame timing, the log, the device, where the player was and your game's own state. Every report lands in your studio console's inbox. Nothing leaves Brain until you set up routing for the game, which can file reports as GitHub issues or post them to a webhook.

Turn reports on in brain/game.toml:

[reports]

The table stays empty: its presence is the switch. A report is sent with the player's Brain account, so the game needs accounts on ([accounts], or any matchmaking queue, leaderboard, saves or products); a guest account is enough. ase games check refuses [reports] without them.

Turning reports on is your studio's decision to collect screenshots and logs from players, so your game's privacy notice should say so.

Add a report button

The SDK ships a ready report dialog: an issue type, a description field, and Send and Cancel. It takes the screenshot before it draws, so the report shows the game, not the dialog.

report_button.visible = Brain.reports.available
Brain.reports.availability_changed.connect(func(on: bool) -> void: report_button.visible = on)
report_button.pressed.connect(Brain.reports.show_dialog)

That's the whole integration. Hide the button while available is false: the player is signed out, or the studio's plan has no reports left. The dialog doesn't open then.

show_dialog takes options and answers what happened:

var result := await Brain.reports.show_dialog({
	kind = Brain.reports.PERFORMANCE,   # the issue type picked at first; bug otherwise
	pause = true,                       # pause the game while it's open; off by default
})
# result.status: sent, queued, cancelled, unavailable, limited, too_large or over_plan

For a "Send feedback" button, offer only those types with kinds = [Brain.reports.FEEDBACK, Brain.reports.FEATURE_REQUEST].

The dialog works with keyboard, gamepad and touch, scales with the window and keeps clear of safe areas. It's dark and in English. Restyle it with a Theme and replace its strings:

Brain.reports.dialog_theme = preload("res://ui/report.tres")
Brain.reports.dialog_strings = {"title": "Tell us what happened", "send": "Send report"}

The theme's type variations are BrainReportTitle and BrainReportMuted (labels) and BrainReportPrimary (the Send button); the issue type is an OptionButton. The string keys are in addons/ase_brain/reports/dialog.gd (STRINGS).

Your own report screen

When your game has its own report screen, use the calls the dialog is built on. Everything the dialog sends, your screen sends too.

# When the player opens your report screen, before it draws: the screenshot shows the game.
var draft := Brain.reports.begin()
if not draft.is_ready():
	await draft.ready
if draft.screenshot_preview:           # null unless the screenshot_preview setting is on
	form.show_preview(draft.screenshot_preview)
await form.confirmed

draft.kind = Brain.reports.BUG          # BUG, CRASH, PERFORMANCE, FEATURE_REQUEST, FEEDBACK, OTHER
draft.description = form.text           # optional, up to 4,000 characters
draft.include_screenshot = form.screenshot_toggle.button_pressed
draft.attach("save_slot", 2)            # for this report only
var result := await draft.submit()      # {status, report_id, error}

submit() answers one of these:

status Means
sent Brain has it; report_id is its id, like R-7K2QMD
queued The device is offline or Brain is busy: the report waits on the device and goes on its own
limited The player reports too often; it's kept and sent shortly
too_large Over a bound below, such as a description that's too long; not kept
unavailable The game has no [reports], or the player is signed out
over_plan Your studio used its plan's reports for the month; available turns false

The device keeps up to 3 unsent reports across restarts, a newer one pushing out the oldest. Brain.reports.queued() counts them.

Your game's state

Tell the SDK what the player is doing, and every report carries it until you change it:

Brain.reports.set_context("quest", "The Sunken Bell")
Brain.reports.set_context("ferry_state", ferry.state_name())
Brain.reports.set_context("quest", null)    # clears one; clear_context() clears all

Values are strings, numbers, bools or flat lists of them, under snake_case keys: at most 32 keys and 16 KB, attach values included. Reports from the dialog carry them too.

Settings

Project settings under ase_brain/reports/ set what the SDK keeps. Code overrides them at any time, for example per platform:

Brain.reports.configure({
	screenshot_px = 1280 if OS.has_feature("mobile") else 1920,
	screenshot_preview = OS.is_debug_build(),
})
Setting Default
frame_history_s 60 Seconds of frame timing kept; 0 turns it off, else 10 to 120
log_lines 200 Log lines kept; 0 turns the log off, at most 500
log_level info Lowest level kept: debug, info, warn or error
screenshot true Take a screenshot of the game
screenshot_px 1920 The screenshot's long edge: 1280 or 1920
screenshot_preview false Also make a texture of it for your own screen (draft.screenshot_preview); the dialog never shows it

A draft can still leave out the screenshot or the log for one report with include_screenshot and include_logs.

What it costs

Only two things run while the game plays: one add per frame into the frame timing, and one copy per log line. Everything else is read once, when the player starts a report, and the screenshot is encoded off the main thread. The addon's README lists the measured costs. On Godot's Compatibility renderer (web and many low-end Android devices) the screenshot is one synchronous read, a few milliseconds, when the player presses Report.

Without [reports] nothing is collected or hooked.

What a report sends

Part What
The player's report The kind and the description
Your game's state set_context and attach values
Build Game, version, commit, debug or release, platform, engine and SDK versions
Session This run's install and session ids, its length, the locale and the input device
Device OS, model, CPU, GPU and renderer, memory, display, refresh rate, window mode and vsync
Frame timing One entry per second: average fps, 1% low, frame times and hitches over 33 and 100 ms
Resources Memory, video memory, draw calls, objects, process and physics time
Where The scene and the place Brain.set_context names
Network Signed in, the match, round trip time, loss and the last network error
Log The last lines and errors with their stacks
Screenshot A JPEG of the game when the report began, up to 1 MB
  • Taken when the player reports. The screenshot, frame timing and log are from the moment begin() ran (or the dialog opened), before any report screen covered the game.
  • Scrubbed on the device. Access tokens, email addresses, IP addresses and the OS user name in file paths are removed from the log before it's sent.
  • No player id. A report never carries the player's id. Brain knows it from the account that sent the report and keeps it beside the report, for your support staff only.

Brain keeps a report for 365 days. It goes sooner, screenshot included, when the player deletes their account, when their account merges into another, or when the game is deleted. A player's data export includes their reports.

Read reports

Every report shows in the studio console, on the game's Reports page, Inbox tab, the moment it arrives. It lists the newest first, with search and filters by kind, version, platform and age, and shows each report the way an issue would: the player's words, the screenshot, then what the kind leads with (the errors of a crash, the frame timing of a performance report), with the device, the log and the raw report folded away. Open the player opens their account on the Play page.

Reports hold players' own words, logs and screenshots, so the inbox needs player support access.

Agents read the same inbox through Brain's MCP server, with the read-only get_reports tool.

Send reports to GitHub or a webhook

Routing is set up per game, on the game's Reports > Routing page. It's off for every new game, and a new game has no destinations: until you turn routing on and add one, reports stay in the inbox. Only members who can manage games change it, and every change is in the audit log. game.toml, the SDK and the MCP tools can't change it.

  • Destinations are where reports go: a GitHub repository or a webhook. Each belongs to that game alone; nothing is shared with your other games. Every report goes to every destination that is on. A GitHub destination takes only reports with a description unless you turn that off, so one-tap reports don't become empty issues; they still reach the inbox.
  • The switch beside the game's reports turns routing on or off for all its destinations at once. Each destination's own switch, settings, check and delete are behind its ... menu.
  • Send to sends one report from the inbox to any of the game's destinations, on or off.
  • Send test report delivers a made-up report to check a destination.

The inbox shows where each report went: the destination, a link to the issue and whether it was delivered or is retrying. A destination that keeps failing says what to fix.

GitHub issues

Brain files issues as your studio's ASE Brain GitHub App, in a repository the App is installed on; the game's own repository is filled in. Your GitHub organization approves the App's Issues permission once, and until it does, the destination says it's waiting for that approval.

Each issue is titled from the player's first sentence, with the version and platform, and filed with your organization's Bug or Feature issue type where you have them. Brain adds no labels and creates none in your repository. Its body leads with what the kind needs, shows the screenshot, and folds the device, log and report JSON. Player text can't mention anyone or link other issues.

Webhook

Brain posts JSON to your HTTPS endpoint, for Jira, Discord or any other tool:

{ "type": "report.created", "version": 1, "report": { }, "document": { } }
  • report is the report as Brain keeps it, with the parts in What a report sends and screenshot_url, the screenshot's link, but without the install id.
  • document is the same report laid out for people, the way Brain files GitHub issues: a title and blocks to render in order. Its text is plain: escape it for your tool.
  • Later versions may add fields; ignore what you don't know.

Every call has two headers:

  • Brain-Signature: t=<unix seconds>,v1=<hex> is HMAC-SHA256 of <t>.<raw body> under the destination's signing secret. Check it, and refuse old timestamps.
  • Brain-Delivery is the same on every attempt of one delivery, so you can drop repeats.

A 408, 429 or 5xx answer, or no answer within 10 seconds, is retried, up to 10 attempts. Any other answer, a redirect included, stops and marks the destination to fix.

  • Link back. Answer 2xx with {"id": "...", "url": "..."} and the inbox links to your copy.
  • Public addresses only. Brain refuses URLs on private or local networks.
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret, maxAgeS = 300) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > maxAgeS) return false;
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Delivered copies are yours

Brain never edits or deletes an issue or webhook call it delivered. When a player's report is deleted from Brain, its copies stay as they are, and only their screenshot stops loading, because the image is Brain's link to the deleted file. Those copies fall under your studio's own privacy policy and deletion process. No copy carries the player's id or the install id.

Limits

Limit Value
Description 4,000 characters
Your game's state 32 keys, 16 KB
Screenshot 1 MB, long edge 1,920 px
Log 500 lines, 48 KB
Whole report 1.5 MB
Per player 2 reports a minute, across all games
Per studio Your plan's reports per month; reporting stops at the limit
Kept 365 days