A player will never see your save code. They will feel the one time it fails.
Lost progress does not read like a technical problem to the person who lost it. It reads like a broken promise. The game said the progress counted, and then it did not.
In my game projects, Instinct does much of the implementation and testing. I stay responsible for direction and for the decision to ship. Save behavior sits exactly on that line: the assistant can build the mechanism, but the promise the mechanism keeps is a design decision. I want that promise written down before anyone writes code.
The failure is rarely the write
Most save systems write data correctly when they get the chance. The gap is timing.
A mobile game lives inside a lifecycle it does not control. The operating system can pause it, freeze it, or kill it to reclaim memory. A call arrives. The battery dies. The player swipes the app away in the middle of a reward animation because something in real life is more urgent than the next level.
If progress only reaches disk when a timer fires or when a scene closes politely, then every one of those exits is a small bet. Most days the bet wins. The day it loses, the player learns the game cannot be trusted, and that lesson is very hard to un-teach.
Write the promise in one sentence
Before choosing a mechanism, I want one sentence: what exactly must survive?
"Level progress and currency survive any exit, including a force-quit in the middle of an action." A sentence like that is short, but it rules things out. A timer that fires two seconds after a move does not satisfy it on its own. Neither does saving only when the player returns to the map.
The sentence also defines what is not promised. A half-finished animation does not need to resume. Cosmetic state can be regenerated. Being explicit here is what makes the real promise cheap enough to keep.
Save at decision points
The pattern I aim for is simple: when the game state changes in a way the player would call progress, that is a save point.
A level completed, a reward claimed, a purchase finished. If the save stays small, writing it often costs little. A few fields written at each decision point are usually a better bet than a full state dump written rarely.
Two mechanisms are worth naming. Writing to a temporary file and renaming it over the old one avoids the specific failure where a crash mid-write leaves a half-written file. It is not magic: durability still depends on the storage stack honoring the write, and the rename has to come after the write completes. Lifecycle callbacks are the second net, not the first. Android's onPause is a useful place to catch a background exit, but once the system decides to reclaim the process, no callback is guaranteed to run. A design that relies on callbacks alone is betting the promise on them.
Test the crash, not just the flow
A save you have not crash-tested is a hope.
The useful test is unglamorous: play, force-stop the app in the middle of the action, relaunch, and compare what survived with what the promise said should survive. Automate the boring version of it. Run it on the happy path and on the embarrassing paths: mid-reward, mid-purchase, mid-first-launch.
The honest limits
Even a good save design cannot guarantee that every byte reaches storage before the power is gone. What it can do is make that outcome rare, and make the promise explicit about what survives and what does not.
This is the same discipline as writing down what a test cannot prove. A persistence test that only exercises clean exits proves almost nothing about a phone in a pocket.
References:
- Godot Engine documentation, Saving games: https://docs.godotengine.org/en/stable/tutorials/io/saving_games.html
- Godot Engine documentation, FileAccess: https://docs.godotengine.org/en/stable/classes/class_fileaccess.html
- Android Developers, The activity lifecycle: https://developer.android.com/guide/components/activities/activity-lifecycle

