Save System
In short
Saves are
JsonUtilityJSON inApplication.persistentDataPath. Every write goes to a.tmpfile that is swapped in withFile.Replace, keeping the old file as.bak; an unreadable file falls back to its.bak.save.jsonholds meta progression;run.jsonis the checkpoint of the run in progress.
How it works
The files
| File | Holds | Written by |
|---|---|---|
save.json | SaveData: points, unlocks, maps, XP, settings, cosmetics, leaderboard | SaveSystem.Save |
run.json | RunSaveData: the solo run checkpoint | SaveSystem.SaveRun |
fit-overrides.json | Fit Mode values saved from a build | FitOverrides.SaveToPersistent (Fit Mode) |
swings.csv | One line per pitch you see, for tuning the batting (not a save: never read back by the game) | SwingLog (Swing Log) |
Crash-safe writes
- Write the JSON to
path.tmp. - If
pathexists,File.Replace(tmp, path, path.bak): one step, the old file becomes the backup. OtherwiseFile.Move(tmp, path). - Reading tries
path, thenpath.bak(logging “was unreadable; restored from backup”). - A failed write only logs an error; the old file is untouched.
SaveSystem.Delete() removes save.json, its .bak and .tmp, and the run files too. FitOverrides writes the same way but keeps no .bak.
SaveData (save.json)
| Field | Meaning |
|---|---|
version | 1 |
playerName | ”You” by default (used on the leaderboard and in the co-op lobby) |
metaPoints, bestScore, runsPlayed, runsWon | Meta totals (Meta Progression) |
selectedBatterId | ”rookie” by default |
unlockedIds | Ids of bought batters, shop items and cosmetics |
leaderboard | LeaderboardEntry list (board, playerName, score, batterId, date); 20 kept per board |
settings | GameSettings (below) |
cosmetics | CosmeticLoadout: hatId, glassesId, shoesId, batId ("" = nothing) |
maps | MapProgress per park: parkId, beaten (0 none, 1 Normal, 2 Medium, 3 Hard), best[] per difficulty |
accountXp | Account XP (#37). Only XP is stored; levels are computed (XP and Levels) |
batterProgress | BatterProgress list: batterId, xp (#37) |
callSignId | The worn call sign (Call Signs) |
GameSettings
| Field | Default | Setting label |
|---|---|---|
showTimingRing | false | TIMING RING |
skipCutscenes | false (cutscenes play) | CUTSCENES (shows ON when false). Also the dev menu’s CUTSCENES tab (Cutscenes). |
noReplays | false (replays play) | REPLAYS (shows ON when false) (Hit Show) |
musicVolume | 0.6 | MUSIC |
sfxVolume | 1 | SOUND |
aimSensitivity | 1 (0.5 to 2.5, steps of 0.05) | AIM SPEED |
showFps | false | FPS COUNTER |
displayMode | 0 (as launched; 1 fullscreen, 2 windowed) | FULLSCREEN / WINDOWED |
resolutionWidth, resolutionHeight | 0 (the screen’s own) | resolution |
vSync | true | V-SYNC |
fpsLimit | 0 (unlimited; only with V-Sync off) | FPS CAP |
A field missing from an old save just gets its default, so new settings need no migration (Add a Setting).
RunSaveData (run.json)
Fields: version (current 2), seed, isDaily, dailyDate, batterId, parkId, difficulty, currentIndex, inShop, coins, totalScore, homeRuns, longestFt, inningScores, upgradeIds.
- Checkpoints:
RunManager.ShowMap(between innings) andRunManager.PrepareShop(on entering the shop; called byOpenShop, and before the BOSS DOWN cutscene so quitting mid-scene resumes in the shop). Nothing is saved mid-inning; quitting mid-inning resumes before it with the same pitches (Seeds and Determinism). - Resume:
RunManager.ContinueRunrebuilds theRunStatefrom the seed, restores totals, marks earlier innings cleared with their scores, replaces the starting kit withupgradeIds, then opens the shop (inShop) or the map. - Dropped when (
CanResumefalse): version is not 2, the batter or park no longer exists, the difficulty is out of range,currentIndexis past the last inning, or a daily from another date. - Deleted when the run ends (
EndRun) or bySaveSystem.Delete. - Co-op runs are never checkpointed (
CanSaveAndQuitis false).
When save.json is written
- Every
MetaProgressionchange that callsPersist(): unlocks, equip / unequip, call sign, map records, every dev-menu action. - Run end (
RunManager.EndRun,CoopRun.EndRun), once for points, XP and leaderboard. This happens before the MAP BEATEN / GAME OVER cutscene, so quitting mid-cutscene loses nothing. - Settings: on toggles (including CUTSCENES and REPLAYS), and when a slider is released. The dev menu’s CUTSCENES: ON / OFF button saves at once too.
GameRoot.OnApplicationQuit.
Numbers
| What | Value | Where to tune |
|---|---|---|
SaveData.version | 1 | SaveData.cs |
RunSaveData.CurrentVersion | 2 (a run is 9 innings at one park, with a difficulty) | RunSaveData.cs |
| Leaderboard entries kept per board | 20 | LocalLeaderboardService.MaxEntriesPerBoard |
| Board names | all-time, daily-yyyy-MM-dd | Boards |
In the code
| File | What it does |
|---|---|
Scripts/Progression/SaveSystem.cs | Load, Save, Delete, HasRun, LoadRun, SaveRun, DeleteRun, Read, Write. |
Scripts/Progression/SaveData.cs | SaveData, BatterProgress, MapProgress, CosmeticLoadout, GameSettings, LeaderboardEntry. |
Scripts/Progression/RunSaveData.cs | The checkpoint shape. |
Scripts/Run/RunManager.cs | Checkpoint, PrepareShop, SavedRun, CanResume, ContinueRun. |
Editor/ContentExporter.cs | Tools > Home Run Derby > Delete Save File. |
Gotchas
No migration code exists
SaveData’s comment says to bumpversionand migrate inSaveSystemif the shape changes, butSaveSystemhas no migration step. Today, adding fields with defaults is the only safe change. Renaming or retyping a field silently drops that data.
JsonUtilitycan’t serializeDictionary; use lists.- Changing the selected batter (
MetaProgression.SelectedBatter) does not save on its own; it is written with the next save or on quit. - Tests build
MetaProgressionwithpersist: false, so they never touch the realsave.json. - In the Editor,
persistentDataPathis shared by every Play session of the project; use Delete Save File to start over. - Where that is on Windows:
%USERPROFILE%\AppData\LocalLow\DefaultCompany\RoguelikeHomeRunDerby\(company and product names fromProjectSettings/ProjectSettings.asset; renaming either moves the saves and players lose progress).
Related
- Meta Progression: what the points and unlocks mean
- XP and Levels: the XP fields
- Seeds and Determinism: why run.json can be so small
- Daily Seed and Leaderboard: the leaderboard boards
- Fit Mode: fit-overrides.json
- Swing Log: swings.csv