Project 08: Persistent system
In Project 07, your mission granted money using an in-memory flag. In Project 08, you build a complete faction reputation engine where your standing with the Families and the Ballas is saved to disk, survives closing the game, and gracefully handles corrupted files without crashing.
The mission
Build a resilient disk persistence system fulfilling six core technical deliverables:
- Serialize and deserialize structured profile data using .NET's native
System.Text.Json. - Automatically create nested directory structures (
scripts/my_mod/) on demand without file system exceptions. - Load profile data lazily on first tick and display an on-screen ticker confirming active values and version.
- Implement two-wave error recovery: generate defaults for missing saves, and recover cleanly from broken JSON syntax.
- Protect against future schema drift using an integer version check (
Profile.CurrentVersion). - Guarantee that state changes are written to disk upon script teardown in
OnAborted.
Specifications, constraints
- Zero-crash resilience: Encapsulate all file I/O and JSON parsing in defensive
try-catchblocks; corrupted files must fall back to safe defaults rather than crashing the game. - Schema versioning: Maintain an explicit integer
Versionfield in the data model to detect incompatible future save formats. - Silent secondary logging: Disk logging to
profile.logmust never throw exceptions or disrupt game execution under any circumstance. - Human-readable storage: Format exported JSON using indented formatting (
new JsonSerializerOptions { WriteIndented = true }). - Clean teardown save: Persist the active profile to disk inside
OnAborted.
Implementation steps
- Define the data transfer model: Create a
Profileclass containingVersion,Reputation, and aDictionary<string, int> FactionStandings. - Declare persistence script: In
JsonPersistence, define constants for the save path and a private_profilefield. - Wire lifecycle events: In the constructor, attach handlers to
TickandAborted. - Implement lazy loading: In
OnTick, check if_profileis null. If so, assign it viaLoadProfile()and post an informational ticker. - Build defensive loading in
LoadProfile:- Check
File.Exists(SavePath). If absent, generateDefaultProfile(), save it to disk, and return it. - Read file text and deserialize into
Profile. - Validate version compatibility: if version does not match
Profile.CurrentVersion, log a warning and return default data. - Catch parsing exceptions: log the failure message and return safe default values.
- Check
- Build defensive saving in
SaveProfile:- Ensure the parent directory exists using
Directory.CreateDirectory. - Serialize with indented formatting and write to disk.
- Ensure the parent directory exists using
- Implement silent logging: Append timestamped strings to
profile.loginside a silenttry-catch. - Save on teardown: In
OnAborted, callSaveProfile(_profile).
APIs, tools to explore
System.Text.Json.JsonSerializer.Serialize<T>(T value, JsonSerializerOptions options): Converts .NET objects into JSON text.System.Text.Json.JsonSerializer.Deserialize<T>(string json): Parses JSON text back into strongly-typed C# objects.System.IO.File.ReadAllText(string path)/File.WriteAllText(string path, string contents): Reads and writes entire text files atomically.System.IO.Directory.CreateDirectory(string path): Recursively creates all directories and subdirectories in the specified path.System.Collections.Generic.Dictionary<TKey, TValue>: Generic collection storing key-value pairs for faction standing scores.
Validation checklist
Your mod is validated when:
- Launching with no save file:
profile.jsonis generated with clean default values, and the ticker announces "Reputation loaded: 100 (v1)". - Inspecting
profile.json: The file is properly indented withVersion: 1,Reputation: 100, and faction standings for Families and Ballas. - Corrupting the file with broken syntax: On reload, the script recovers smoothly without crashing, defaults to 100, and writes an error entry to
profile.log. - Changing
"Version": 99in the JSON: The script flags the mismatch, falls back to defaults, and continues running cleanly. - Reloading with
Insert: The active profile saves to disk without data corruption.
Solution, explanations
Partner
Verified solution and code explanations
The mission, specifications, and guided steps remain 100% free and open for everyone. The complete verified reference code and production explanations are reserved for Partner members.