Quick answer: The Playdate is a handheld console with a Lua and C SDK, limited memory, and a crank as a unique input. Crashes happen on a device that is usually offline, so you capture the Lua error or C fault and device state into a stored buffer on the device, then report it when the player next syncs or connects, and group the duplicates in one dashboard.
The Playdate is a small yellow handheld with a black and white screen, a Lua and C SDK, and a crank that no other platform has. Developing for it means living with tight memory, a real device that is usually offline, and a simulator that does not perfectly match hardware. Crashes are a particular challenge because the device is not connected to the internet during normal play, so the usual report-immediately model does not apply. A Lua error or a C fault happens in someone's pocket, far from any network. This post covers how Playdate games crash, the offline constraint, and how to capture and later report errors with device context.
How Playdate games crash
Most Playdate games use the Lua SDK, where your code runs in the playdate.update callback each frame. An uncaught Lua error there, a nil index, arithmetic on nil, or a bad call, halts the game and the device shows a crash screen with the error and a short trace. Games written against the C SDK instead can fault at the native level, with hard faults from bad pointers or memory access on the Cortex M7 processor, which are harder to interpret than a Lua line. Mixed projects can hit either kind.
Memory is a constant pressure. The Playdate has a modest RAM budget, and exhausting it produces out-of-memory failures that present as crashes or as the system reclaiming your game. Lua's garbage collector adds timing variance, so a memory-related crash may depend on collection pauses that differ between runs. Because the screen is one bit per pixel and the CPU is modest, performance and memory bugs surface as instability under load. Knowing whether a crash is a Lua error, a C fault, or memory exhaustion is the first triage step.
The offline constraint
The defining challenge is that a Playdate is almost never online during play. It connects over USB to a computer or syncs through the Playdate servers at specific times, but during a session in a backpack or on a couch there is no network. The report-on-crash model that web and connected games rely on simply does not work. If you want to learn about a crash that happened yesterday on the bus, you have to have stored evidence of it on the device and deliver that evidence later when a connection exists.
This inverts the usual design. Instead of a live transport, you need durable local capture: when a crash occurs, write the error, trace, and device state into persistent storage using the SDK's datastore so it survives the crash and a restart. Then, at a moment when connectivity is plausible, such as the next launch with a sideloading bridge or a companion flow, read those stored reports and send them. The capture and the delivery are decoupled by hours or days, which is unusual but entirely workable once you design for it.
Capturing Lua and C errors
For Lua, the tool is pcall. Wrap your update logic, or a dispatch that calls it, in pcall so that when it errors, you receive the error message instead of letting the crash screen take over uncontrolled. From there you can capture the message and, with the debug facilities the SDK exposes, a trace, then write that into datastore before deciding whether to recover or stop. This lets you record the crash on your own terms and show the player a friendlier message than the raw system crash screen.
For C SDK code, capture is lower level. You can install handlers for faults where the SDK and platform allow, and at minimum maintain a breadcrumb in persistent storage that records the active scene, recent actions, and memory figures, updated as the game runs, so that even a hard fault leaves a trail you can read after a restart. Because C faults give you addresses rather than friendly lines, that breadcrumb is often the most actionable thing you have. In both cases the principle is the same: write durable evidence before the device forgets.
Device context that matters
The Playdate's unique hardware shapes which context is worth capturing. Record memory usage and the high-water mark, since memory exhaustion is a leading crash cause on this constrained device. Capture the firmware or system version, because behavior and available memory shift between OS updates. Note whether the crash happened in the simulator or on hardware, as the two diverge and a simulator-only crash means something different from one only real devices hit.
Crank state deserves special mention. The crank is a Playdate-exclusive analog input read through playdate.getCrankPosition and related functions, and games that drive mechanics from it can crash in crank-handling code that no other platform exercises. Recording whether the crank was docked or in use, and the recent crank delta, can explain a class of bugs unique to the device. Add the active scene and a short action breadcrumb, and a stored Playdate report carries enough about a session that happened offline hours ago for you to reconstruct it once it finally reaches you.
Setting it up with Bugnet
Bugnet fits the Playdate's offline reality through decoupled capture and delivery. On the device, your pcall wrapper and breadcrumb write the Lua error or C fault context into datastore. Then, when the game next runs in a context with connectivity, through a sideloading bridge or a companion path, you read those stored reports and hand them to Bugnet, which records each as a crash with the trace and the device fields: memory, firmware, simulator-or-hardware, and crank state. An in-game report prompt can also let a player flag a non-fatal issue, queued the same way until a connection exists.
On the dashboard, Bugnet groups identical crashes into one issue with an occurrence count, so the same nil index across many devices is a single ranked item rather than scattered crash screens you never see. Custom fields for firmware, simulator versus hardware, and memory high-water mark let you filter: if a crash only hits one firmware version, or only above a memory threshold, the grouped view shows it. For a platform where you cannot watch crashes live, that delayed-but-grouped picture is exactly what makes the offline device tractable to support.
Testing on hardware, not just the simulator
The simulator is fast and convenient, and that is precisely why it is dangerous to rely on alone. It has more memory and a more forgiving runtime than the device, so memory-exhaustion crashes and timing-sensitive C faults often appear only on real hardware. Build your capture path and then deliberately trigger a Lua error and, if you ship C code, a fault on an actual Playdate, confirming each writes to datastore and later delivers to your dashboard with the right context. Exercise crank-driven code on the device, since the simulator's crank emulation is not the same as turning the real crank.
Plan your delivery moments explicitly. Because reports queue on the device, decide when they flush, on next launch, on a connected build, or through a companion step, and verify queued reports survive restarts and battery deaths. Test with several stored reports to confirm none are lost or duplicated. With durable capture, hardware-verified, and grouped delivery, supporting a Playdate game stops depending on players emailing you about a crash screen and becomes a quiet, reliable stream you review whenever the little yellow devices reconnect.
The Playdate crashes in your pocket, far from any network. Persist the error and device state to datastore, deliver when it reconnects, and group what arrives.