Player reports
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": { } }
reportis the report as Brain keeps it, with the parts in What a report sends andscreenshot_url, the screenshot's link, but without the install id.documentis the same report laid out for people, the way Brain files GitHub issues: atitleandblocksto 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-Deliveryis 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 |