Quick answer: Data-driven games fail in two distinct ways: a script error inside your embedded language and a native engine crash. They need different captures and different fixes, so tag every report with which side faulted, include the script name and line for script errors, and attach the native trace plus the script that was running for engine crashes.

When most of your game lives in scripts and data files, a content author can break the build without touching a line of native code, and your crash reporting has to account for that. A bad table reference in a quest script and a null dereference in your C++ binding layer are completely different failures with completely different owners, yet both end the frame the same way. This post is about keeping those two worlds legible: capturing script errors with the script context that explains them, capturing native crashes with the script state that triggered them, and never confusing one for the other.

Two failure modes, one game

A data-driven game runs an embedded language, Lua, a custom DSL, or similar, on top of native engine code, and faults can originate on either side. A script error is logic your interpreter caught: a nil index, a wrong argument count, a runtime type mismatch in the script. A native crash is a fault in your engine or in the binding layer that exposes engine functions to scripts. The first is recoverable in principle and points at content; the second usually kills the process and points at your code.

The danger is collapsing both into a generic crash. A script error wrapped poorly can surface as a native fault, and a native crash triggered by script input can look like a content bug. If your reporting does not record which side raised the error, you will route quest-script typos to engine programmers and engine bugs to content authors, and both will bounce the ticket. The single most valuable field in a data-driven crash report is which layer faulted.

Capturing script errors with their context

When your interpreter raises an error, you already have the richest possible context: the script file, the line, the function, and the language-level stack within the embedded VM. Capture all of it. A script error report should read almost like a compiler message, naming the exact file and line in the content that failed, because the person who fixes it is a designer or scripter, not a systems programmer, and they need to find the offending data fast.

Add the inputs that drove the script into the failing branch: the quest id, the entity involved, the state flags that were set. Data-driven bugs are notoriously dependent on game state, because the same script runs fine until a specific combination of saved flags routes it through an untested path. The script trace tells you where it broke; the state tells you why it got there. Together they let an author reproduce the failure deterministically instead of chasing an intermittent report.

Capturing native crashes triggered by scripts

When the fault is native, your binding layer is the usual suspect, because that is where untyped script values meet typed engine code. A script passes a handle that was already freed, an out-of-range index, or a value the binding assumed was always valid. The native stack trace shows the engine function that crashed, but on its own it does not show which script called it with bad input. You need both halves: the native trace and the script call site that reached it.

Record the script context at the binding boundary so that when a native function faults, you know which script function and line invoked it and with what arguments. This is the bridge between the two worlds. A native trace ending in your set-entity-position binding is ambiguous until you see that a specific movement script called it with a destroyed entity. Capturing the script call site alongside the native frames turns an inscrutable engine crash into an obvious content-plus-binding bug you can fix on both sides.

Keeping content authors and engineers unblocked

The reason to separate these cleanly is workflow. Content authors iterate on data many times a day and will generate script errors constantly; that is healthy, and those reports should flow to them with enough detail to self-serve. Engine programmers need the native crashes filtered out from the noise of script typos so they can focus on real systems bugs. A report that clearly states its layer lets each group see only what they own and trust that the routing is correct.

This separation also shapes severity. A script error that the VM caught may degrade one quest while the rest of the game runs, whereas a native crash takes everything down. Tagging the layer lets you triage accordingly: native crashes are usually urgent, script errors are usually scoped to the content that broke. Without the distinction every fault looks equally catastrophic, and a small team drowns in alarms when most of them are a designer's typo in a dialogue table.

Setting it up with Bugnet

Bugnet handles both shapes through one pipeline. Native crashes arrive with stack traces and device context; script errors arrive as reports carrying the script file, line, and VM stack you captured. Put the failing layer in a custom field, script or engine, and the rest of your fields carry the quest id, entity, and state flags that explain the path taken. The in-game report button captures game state automatically, so even a non-fatal script glitch a tester notices comes in with the surrounding context attached.

Occurrence grouping is a strong fit because a single broken content file generates the same script error from many players at once. Folding those into one counted issue shows you which data file is hurting and how widely, and filtering by the layer field instantly separates content bugs from engine bugs on the dashboard. You sort the script errors to your authors and the native crashes to your engineers from one view, which is exactly the routing a data-driven game needs to keep both teams moving.

Build the boundary into your tooling

The clean split between script and engine errors should be baked into your error handling, not reconstructed after the fact. Wrap your interpreter so every caught error is tagged as a script fault with full VM context, and instrument your binding layer so every native fault records the script call site that reached it. Once that plumbing exists, every report self-classifies and routes itself, and you stop spending triage time deciding who owns a ticket.

Make script errors visible during development, not just in production. A data-driven game lives or dies by how fast authors can find their own mistakes, so surface the file and line prominently in your tools and in every report. The same context that helps a player's crash get fixed helps a designer fix their own content before it ships. Treat the script-versus-engine distinction as a permanent feature of your reporting and both halves of your team stay unblocked.

In a data-driven game, the first question is always which layer broke. Tag the script-versus-engine boundary and every report routes itself.