Skip to main content

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:

  1. Serialize and deserialize structured profile data using .NET's native System.Text.Json.
  2. Automatically create nested directory structures (scripts/my_mod/) on demand without file system exceptions.
  3. Load profile data lazily on first tick and display an on-screen ticker confirming active values and version.
  4. Implement two-wave error recovery: generate defaults for missing saves, and recover cleanly from broken JSON syntax.
  5. Protect against future schema drift using an integer version check (Profile.CurrentVersion).
  6. 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-catch blocks; corrupted files must fall back to safe defaults rather than crashing the game.
  • Schema versioning: Maintain an explicit integer Version field in the data model to detect incompatible future save formats.
  • Silent secondary logging: Disk logging to profile.log must 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

  1. Define the data transfer model: Create a Profile class containing Version, Reputation, and a Dictionary<string, int> FactionStandings.
  2. Declare persistence script: In JsonPersistence, define constants for the save path and a private _profile field.
  3. Wire lifecycle events: In the constructor, attach handlers to Tick and Aborted.
  4. Implement lazy loading: In OnTick, check if _profile is null. If so, assign it via LoadProfile() and post an informational ticker.
  5. Build defensive loading in LoadProfile:
    • Check File.Exists(SavePath). If absent, generate DefaultProfile(), 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.
  6. Build defensive saving in SaveProfile:
    • Ensure the parent directory exists using Directory.CreateDirectory.
    • Serialize with indented formatting and write to disk.
  7. Implement silent logging: Append timestamped strings to profile.log inside a silent try-catch.
  8. Save on teardown: In OnAborted, call SaveProfile(_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.json is generated with clean default values, and the ticker announces "Reputation loaded: 100 (v1)".
  • Inspecting profile.json: The file is properly indented with Version: 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": 99 in 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.