Dev Log 3: Making Unity Save Files More Reliable

Save systems look simple until players depend on them.Writing JSON to disk is easy. Keeping save data compatible, diagnosable, and recoverable after months of development is the harder part.

That is why LoL Engine’s save system became one of the most important production systems in the package.

 

The Baseline

LoL Engine includes a data persistence service with:
– Save slots
– Async save/load operations
– Compression and encryption support
– Auto-save and quick-save patterns
– Serialization helpers
– Save events
– Configurable save behavior

Those features are useful, but they are only the beginning. The changelog shows several rounds of hardening where real edge cases shaped the system.

Fixing the Hidden Serialization Problem

In 0.17.6-beta, a bug was fixed in the serializer contract resolver. Private Unity fields marked with [SerializeField] were not actually being included by Newtonsoft’s default member discovery.

That meant important fields such as checksum and version data could be missing from written save files. The load path could still proceed, but integrity warnings appeared because the data needed for validation had not round-tripped correctly.

The fix was to include private [SerializeField] fields through the type hierarchy and add a regression test proving the JSON contained the expected fields.

That is a good example of why save systems need tests that inspect the saved data, not only tests that call Save() and Load().

Pluggable Save Storage

Version 0.21.0-beta added ISaveStore, a storage abstraction for save I/O.

The default implementation is LocalFileSaveStore, which uses local files and atomic write-via-temp behavior. There is also an InMemorySaveStore for integration tests.

This matters because the save system should not be welded to File.* calls. Once storage is abstracted, tests can exercise save/load behavior without touching disk, and future projects can route saves through different backends.

Per-Domain Schema Migration

The same release added per-domain schema versions and IDomainMigration.

A save file is rarely one blob of one kind of data. Player settings, inventory, world state, progression, and runtime systems may evolve at different speeds. If every change bumps one global save version, migration logic gets messy quickly.

LoL Engine lets each persistable domain declare its own schema version. When a save loads, the system can run migration chains for the specific domains that changed.

That keeps migration focused:

Inventory v1 -> v2
Settings v1 -> v2
Progression stays v1

Corrupt-File Quarantine

Another 0.21.0-beta feature was corrupt-file quarantine.

When a save fails because of JSON parse failure, checksum mismatch, or incompatible bundle schema, the save system moves the bad file into a quarantine folder instead of silently deleting it or leaving the player with no explanation.

In development, a quarantined file gives you something to inspect. In production support, it gives the player a chance to recover or send the file for diagnosis.

Load Reports

SaveLoadReport records what happened during load:
– Overall success or failure
– Quarantine reason when applicable
– Per-domain load results
– Stored and current schema versions
– Whether a migration ran

That turns “load failed” into useful information.

It also helps UI. A game can decide whether to show a generic failure message, a partial-load warning, or a migration notice.

Partial Success Instead of All-or-Nothing Failure

Version 0.22.0-beta hardened migration and deserialization failures so they are caught per domain. A failure in one domain no longer has to crash the entire load operation.

That is the kind of behavior players feel even if they never see the code. If one optional domain fails but the rest of the save can load, the system should preserve as much state as it safely can and report exactly what happened.

Why This Belongs in a Framework

Every serious Unity project needs save infrastructure, but most games should not spend their early development time rediscovering the same save-system failure modes.

LoL Engine’s save system tries to provide the boring parts:

– Version tracking
– Migration hooks
– Integrity checks
– Quarantine
– Reports
– Async operations
– Testable storage backends

Your game still owns its save data. The framework owns the mechanics around storing and recovering it.

LoL Engine will be available for Unity 6000.0+.

– Asset Store: TBD
– Documentation: TBD