What to Put in a Bug Report Snapshot of Game State
Four buckets for game state — environment, session, moment, and repro clues — ranked by cost versus value, what to leave out, and the traps you hit when serializing it.
- unity
- bug-report
- performance
A screenshot-only report doesn't lead to reproduction, for the reason covered in why bugs fail to reproduce: the conditions get lost wholesale. What's on screen is the outcome. The cause lives in the game state at that moment. This post lays out what to put in that state, and how far to go.
Split it into four buckets
Trying to capture everything leaves you not knowing where to start. Split it into four and each bucket answers a specific question.
| Bucket | What it holds | Question it answers |
|---|---|---|
| Environment | Device model, OS, GPU, Unity version, build number | Is this specific to a device or version? |
| Session | Scene name, playtime, progress | Where, and after how much play? |
| Moment | FPS, memory, last N log lines | What was the state right before it broke? |
| Repro clues | Recent input history, random seed | What inputs, in what order? |
Environment and session are a one-time grab at capture time. Moment keeps changing, so you need the latest value right before capture. Repro clues only work if your game logic has been accumulating them all along, ready to be pulled at capture time. Miss that distinction and the capture code runs fine while the value comes back empty — start collecting input history only at the moment of capture, for instance, and there's nothing from before that moment for it to hold.
Ranking by cost versus value
These don't cost the same. Add them in order, cheapest first.
- Environment info — a few lines of
SystemInfoandApplication.version. Near-zero cost, value shows up immediately (it instantly filters "does this only happen on this device"). - Scene name and recent logs — code already covered in the minimal capture guide. Low cost.
- FPS and memory — a handful of calculations. Even for non-performance bugs, this gives you free context like "the frame rate was already tanking right then."
- Project-specific progress — inventory count, quest state, connected server. Cost rises here because you have to design what's worth pulling for your specific project.
- Recent input history and seed value — contributes the most directly to reproduction, but also costs the most, since you need a new structure that keeps accumulating input in the first place.
Items 1–3 are a day of work and show up in your reproduction rate immediately. Items 4–5 touch your project's own logic, so priority depends on your team's situation. Take a team with five QA people pulling a build twice a week: shipping just items 1–3 already visibly shrinks the pile of tickets that used to close as "can't reproduce," and items 4–5 can wait until you've worked out the design against your own project logic.
What not to include
The bigger the snapshot gets, the more tempting it is to add more, but three things need a hard line.
- Personal information: email, real names, payment details. Device identifiers also need a platform-policy and privacy check before they go in.
- Auth tokens: a session token or API key leaking into a snapshot turns the bug report itself into a security incident. The safest approach is an explicit whitelist of fields to serialize, so auth-related fields are never in the candidate set to begin with.
- Excessive dumps: dumping the entire scene object tree or a full save file. Once a single report crosses a few megabytes, send failures rise and the values that actually matter get buried in noise.
Traps in serialization
- Main-thread-only APIs: most of
TimeandSystemInfoare main-thread only. If the capture trigger fires on a worker thread (an exception callback, for instance), reading them throws. Either queue the capture logic to run on the main thread, or read from a cached value the main thread refreshes periodically. - JSON size: serializing a list or dictionary as-is grows without any item cap. Anything that keeps accumulating, like recent input history, needs to be trimmed to the last N entries at capture time.
- Circular references: hand a
GameObjector a custom class straight to a serializer and references pointing back at each other either loop forever or throw. Pull out plain values into a flat DTO rather than serializing the original object.
// Don't serialize the original object — copy out flat values only
struct StateSnapshot
{
public string scene;
public float fps;
public long memoryMB;
public string[] recentInputs; // last N entries, not the live input queue
}
Summary
Environment is cheap and immediately useful, moment costs a bit more, and repro clues cost the most but matter the most. Add them in that order and stopping at any stage still leaves you ahead. Keep personal information and auth tokens out of the collection set entirely, and serialize a DTO of extracted values rather than the original object. The full picture including trigger and delivery is in part 1; the minimal code to attach this alongside logs is in the minimal capture guide.
Our own Rekon ties this snapshot automatically to the same timeline as video and logs. If you're designing your own, follow the priority order above and start with environment info.